🦁
RailsでSeedを実行する順番を定めてくれるスクリプトを考える
方法
Application Recordのリレーションに従ってテーブルをトポロジカルソートを行えばSeedを実行する順番を定められるのではという考えの元タスクを実装しました。
前提
Seedはテーブルごとに実行されている。
トポロジカルソートとは
有向非巡回グラフの各ノードを順序付けして、どのノードも出力辺の先にノードより前に来るように並べることです。
Rubyでは標準ライブラリにトポロジカルソートがあるためそれを使って実装する事ができます。
HashにTSortをincludeすることによって実装ができます。
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を実行していきます。
参考
Discussion