Madogiwa Blog

主に技術系の学習メモに使っていきます。

FactoryBotのinitialize_withを使って初期化時の挙動をfind_or_initialize等に差し替える方法MEMO

一意制約をかけつつ一定の範囲内にある値を扱いたいときにFactoryBotの単純なsequenceでは対応できなくて、いい感じのやり方無いかなと思って調べていたらinitialize_withを使うといい感じにfind_or_initializeに差し替えて一意制約を回避しつつ範囲内の値を生成できたので使い方をメモしておきます。

github.com

「やりたいこと」と「問題点」

例えば以下のような一意のステータスを管理するモデルがあったとします。

class Master::Status
   validates :name, :inclusion: { in: %w(draft wait approved) }, uniqueness: true   
end

単純にFactoryを作ってしまうと以下のような形になります。

FactoryBot.define do
  factory :master_status do
    name { 'draft' }
  end
end

しかし上記のFactoryでは以下のような問題があり使いにくいです 😢

  • 単純にcreate(:master_status)をして行くと一意制約に引っかかりエラーが発生してしまう
  • 上記のエラーを回避するためにFactoryの生成前に存在チェック等の処理をいちいち行わないと行けない

initialize_withを使った解決

そんなときにinitialize_withを使って生成処理をfind_or_initialize_byに差し替えて上げると便利です✨

FactoryBot.define do
  factory :master_status do
    initialize_with { Master::Status.find_or_initialize_by(name: name) }

    name { 'draft' }
  end
end

find_or_initialize_byに差し替わったことにより、DBに値が入っている場合にはfindされ、なければnewされるので、DBの状態を気にせずMaster::StatusをFactoryBotで生成することが出来るようになりました🙌

おわりに

FactoryBot、あんまりちゃんとドキュメント読んでなかったのですが、読んでみると色々いい感じの機能がありますね🤖

参考

stackoverflow.com