seed-fu (2.3.7)のコードリーディングをしました。

まず、SeedFu::Railtieでタスクを定義したりfixtureのパスを設定します。

rakeタスクはlib/tasks/seed_fu.rakeに定義されています。

SeedFu.seedはSeedRu::Runner#runを実行します

SeedFu::Runner#runはfixturesのパスに入っているファイル分だけ#run_fileを実行します

run_fileはファイルの中身をevalします。(# BREAK EVALすると、そこでchunkをevalして再度chunkを作っていくみたいですね。大きいファイルを読み出す時の機能らしいです。初めて知った…)

fixtureに記述されるseedメソッドを見ていきます。SeedFu::Seeder#seedを呼び出します。

SeedFu::Seeder#seedは以下のようになっています。

@model_classはseedを呼び出したActiveRecord::BaseなのでActiveRecord::Base.transactionが呼ばれます。ハッシュの配列が@dataに入っており、それらの要素に対して#seed_recordが呼ばれます。

#seed_recordは#find_or_initialize_recordでレコードを取得、なければ新規にインスタンス生成を行い、ActiveRecord::Base#assign_attributesしてから#saveでvalidate無しで保存します。

#find_or_initialize_recordは#constraint_conditionsでconstraintsとデータからwhere句のハッシュを生成します。constraintsが[:id]でdataが {id: 1, name: 'hoge'}の場合は{id: 1}がwhere句になります。

SeedFu::Writer

seed-fuにはfixtureファイルをジェネレートするSeedFu::Writerのクラスがあります。

seed_headerで モデルクラス.seed(constraintsの部分を生成しつつ、block内のwriter.addでconstraints以降の引数を設定しています。writer.addの引数のハッシュは#inspectの文字列がそのまま書き出されます。

個人的にはSeedFu::Writerでfixtureをジェネレートするよりは、元のデータをCSVなどに変換したものをデータソースとしてfixtureからそのデータソースを読み出して#seedを呼び出す方がfixtureのファイルの可読性やデータの再利用性の点で良いと思います。

補足

#seedには色々なパターンで引数を渡せるのですが、そこらへんをよしなにやってくれるのがSeedFu::ActiveRecordExtension#parse_seed_fu_argsです。

例えば以下のようにconstraintsとブロックを渡してブロック内でパラメータを設定した場合を考えてみます。

この場合は、#parse_seed_fu_argsは[args, [SeedFu::BlockHash.new(block).to_hash]]が返ります。

SeedFu::BlockHashは以下のように定義されており、渡されたblockを自身を引数に呼び出します。block内はself.xxx=のアクセサメソッドで書き込まれるのでその度にmethod_missingが呼ばれて@hashに値が格納されます。こうすることでblock内での処理をhashに置き換えることができます。

最終的にSeedFu::Seederのコンストラクタには self, args, ブロックから生成したハッシュの配列が入ります。このようにしてconstraintsとハッシュ配列の生成に対応しています。