@ledsun blog

無味の味は佳境に入らざればすなわち知れず

FactoryBotのソースコードを読んだ

背景

最近、コードリーディングが話題になりました。

note.com

blog.riywo.com

ソースコードが読めることは武器になるようです。というわけでソースコードを読みます。

お題

MySQLのカラムの型によって、FactoryBot.buildSQLが発行されるときとされないときがある現象に出会いました。

この挙動がFactoryBotのものかActiveRecordの物か切り分けます。 いきなりActiveRecordソースコードに突入するのは大変なので、 より小さい方のFactoryBotのソースコードを読みました。

FactoryBot内に振る舞えを変えるソースコードがあればよし。 なくてもActiveRecordのどのメソッドを呼んでいるかわかれば、ActiveRecordソースコードを読むときの入り口が決定できます。

結論

今回、読みたいのは、FactoryBot.buildを実行している箇所です。

AttributeAssigner#objectでした。

def object
  @evaluator.instance = build_class_instance
  build_class_instance.tap do |instance|
    attributes_to_set_on_instance.each do |attribute|
      instance.public_send("#{attribute}=", get(attribute))
      @attribute_names_assigned << attribute
    end
  end
end

感想

FactoryBotは、古き良きデザインパターンが駆使されていました。 次のような構成です。

  • FactoryBot.buildFactoryBot.createを表すStrategyインターフェース
  • Strategyを決定するStrategyCalculator
  • それを使ってStrategyとEvaluationを組み合わせて生成するFacotry*1

まあ、ここまでみてもAttributeAssigner出てこないんですよ?エグくないですか? AttributeAssignerはEvaluationから呼び出されます。 EvaluationはFactoryBot.createの実行を、AttributeAssignerはFactoryBot.buildの実行を担っています。 「は?」っておもいませんか?「Strategyでわけたはずじゃ?」って。

Strategyでわかれているのは、次の部分です。

  • createはbuildしてからcreateする
  • buildはbuildだけする

buildとcreateの実体はEvaluationとAttributeAssignerにあります。

createの実体はさらにエグいです。FactoryBotではcreateで実行するメソッドをto_createというメソッドで変更できます。変更しない場合のデフォルトの設定はConfigurationクラスに定義されています。

https://github.com/thoughtbot/factory_bot/blob/5f1a1de114bb390bc397d8d512c149502581f56a/lib/factory_bot/configuration.rb#L22

to_create(&:save!)

はい。save!です。

Rubyでここまで古典的なデザインパターンをきっちり守って書かれているソースコードも珍しいように思います。

*1:FactoryBotの中核がFactoryクラスなの、なかなか洒落が効いてますね。このためだけにFacotryパターンを採用したいくらいです