一意制約をかけつつ一定の範囲内にある値を扱いたいときにFactoryBotの単純なsequenceでは対応できなくて、いい感じのやり方無いかなと思って調べていたらinitialize_with
を使うといい感じにfind_or_initialize
に差し替えて一意制約を回避しつつ範囲内の値を生成できたので使い方をメモしておきます。
「やりたいこと」と「問題点」
例えば以下のような一意のステータスを管理するモデルがあったとします。
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、あんまりちゃんとドキュメント読んでなかったのですが、読んでみると色々いい感じの機能がありますね🤖