🦁

RailsでSeedを実行する順番を定めてくれるスクリプトを考える

2024/06/23に公開

方法

Application Recordのリレーションに従ってテーブルをトポロジカルソートを行えばSeedを実行する順番を定められるのではという考えの元タスクを実装しました。

前提

Seedはテーブルごとに実行されている。

トポロジカルソートとは

有向非巡回グラフの各ノードを順序付けして、どのノードも出力辺の先にノードより前に来るように並べることです。
Rubyでは標準ライブラリにトポロジカルソートがあるためそれを使って実装する事ができます。
HashにTSortをincludeすることによって実装ができます。
https://docs.ruby-lang.org/ja/latest/library/tsort.html

require 'tsort'

class Hash
  include TSort
  alias tsort_each_node each_key
  def tsort_each_child(node, &)
    fetch(node).each(&)
  end
end

{1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort
#=> [3, 2, 1, 4]

Rakeタスクの実装

require 'tsort'

class Hash
  include TSort
  alias tsort_each_node each_key
  def tsort_each_child(node, &)
    fetch(node).each(&)
  end
end

namespace :sort do
  task apply: :environment do
    Rails.application.eager_load!
    base = ActiveRecord::Base.descendants
                             .select { |k| !k.abstract_class? }
                             .flat_map { |k| k.reflect_on_all_associations }
                             .uniq
                             .filter do |r|
      r.is_a?(ActiveRecord::Reflection::AssociationReflection) && r.is_a?(ActiveRecord::Reflection::BelongsToReflection) && !r.polymorphic?
    end

    sort = {}
    base.each do |r|
      sort[r.active_record.to_s.underscore].nil? && sort[r.active_record.to_s.underscore] = []
      sort[r.name.to_s.singularize].nil? && sort[r.name.to_s.singularize] = []
      sort[r.active_record.to_s.underscore] << r.name.to_s.singularize
    end

    puts sort.tsort
  end
end

解説していきます

Rails.application.eager_load!

一旦すべてのリレーションをここで読み込んでいます

ActiveRecord::Base.descendants
                         .select { |k| !k.abstract_class? }
                         .flat_map { |k| k.reflect_on_all_associations }

全てのActiveRecordクラスを列挙して、すべてのサブクラスの関連を取得しています。

      .filter do |r|
      r.is_a?(ActiveRecord::Reflection::AssociationReflection) && r.is_a?(ActiveRecord::Reflection::BelongsToReflection) && !r.polymorphic?

has_one has_many関連のみをfilterで取得しています。

    sort = {}
    base.each do |r|
      sort[r.active_record.to_s.underscore].nil? && sort[r.active_record.to_s.underscore] = []
      sort[r.name.to_s.singularize].nil? && sort[r.name.to_s.singularize] = []
      sort[r.active_record.to_s.underscore] << r.name.to_s.singularize
    end

テーブルの関連をハッシュにして配列で関連を表現しています。

puts sort.tsort

最後にトポロジーソートを行い順序付けされたものを取得して出力しています。
出力されたものに従ってseedを実行していきます。

参考

https://en-jp.wantedly.com/companies/wantedly/post_articles/533776
https://docs.ruby-lang.org/ja/latest/library/tsort.html
https://ja.wikipedia.org/wiki/トポロジカルソート#:~:text=トポロジカルソート(英%3A topological sort,ソートすることができる。

GitHubで編集を提案
SMARTCAMP Engineer Blog

Discussion