今回はRailsでテストデータを作成する”factroy_bot”に関する小ネタです。
TL DR;
こんな感じでイケちゃう
User.account_type.values.each do |type|
trait :"#{type}" do
account_type { type }
end
end
動作環境
- Rails: 5.2.3
- Ruby: 2.6.5
- factory_bot: 5.0.2
- factory_bot_rails: 5.0.1
※今回はRubyの記法に依る部分が大きいので、上記バージョンはあまり気にしなくても良いです
コード例
想定するモデル
この記事では以下のようなUser
モデルを例として考えます。プロダクトコードによくある「複数のユーザー種類をenumのカラム(今回は例としてaccount_type
とする)で持ち判別する」モデルです。
class User < ApplicationRecord
validates :name, presence: true
# 中略
extend Enumerize
enumerize :account_type, in: { normal: 0, admin: 1, client: 2, development: 3 }, scope: true
end
どうでもいい話ですが、色々な種類のユーザーが存在し、しかも同じユーザーが複数の種類を持つといった、ユーザー周りが複雑になるのはあるあるですが、本当にツラいですよね。AdminかEndUserかの2択ぐらいで収まれば、それぞれのユーザーから見える画面もきれいに分割できて丁度いいのですが...。
普通にfactoryを書く場合
さて、このようなenumカラムを持つUserモデルを素直にfacrotyで表現すると、このようになります。ああめんどくさい。モデルが複雑になれば、factoryが複雑になるのは当然のことですが...
FactoryBot.define do
factory :user do
name { 'test' }
# 中略
trait :normal do
account_type { normal }
end
trait :admin do
account_type { admin }
end
trait :client do
account_type { client }
end
trait :development do
account_type { development }
end
end
end
やりたいこと
こんな意味のないコードをチマチマ全部書くのはやりたくない。いやでも書くしか無いし、一旦Pushするか...。いや、なんかそれらしいカッコいいやり方があるはずだ。多分。ほんの数行で全部のenumのtraitを生み出すやり方が。ついでに、カラムのenum定義が増えたときにも勝手に増えるようにしてほしい。忘れそうだし。
小技
そこで小技を使うと、こんな感じで書けます。「まさか動かないだろう」と思い、冗談で書いたら動きました。Rubyってすごい。
User.account_type.values.each do |type|
trait :"#{type}" do
account_type { type }
end
end
User.account_type.values
と、enumのカラムから直接traitを作っているので、enumの定義が増えたときにテストデータを作り忘れることもありません。ユーザーの氏名やアドレスを表すカラムがあれば、そこに同様にtype
変数を突っ込めば、RSpecのテスタビリティも向上しそうです。
ちなみに、User.account_type.values
の部分はこんな感じで動作しています。ハッシュでenumを取り出して、そのvalueをvalues
メソッドで配列化し、その配列の値でtraitを定義しているわけです。もちろん必要であれば、keyとvalueの両方を取り出して使うこともできます。
[1] pry(main)> User.account_type.values
=> ["normal", "admin", "client", "development"]
[2] pry(main)> User.account_type
=> #<Enumerize::Attribute:0x0000558ff649f058
@i18n_scopes=["enumerize.user.account_type"],
@klass=User (call 'User.connection' to establish a connection),
@name=:account_type,
@skip_validations_value=false,
@value_hash=
{"0"=>"normal",
"1"=>"admin",
"2"=>"client",
"3"=>"development",
"normal"=>"normal",
"admin"=>"admin",
"client"=>"client",
"development"=>"development"},
@values=["normal", "admin", "client", "development"]>
余談...
まあ、実際のプロダクトコードでは、各enumごとに紐付くアソシエーションも変更する必要があったりするので、なかなかこれ一つで完璧にtraitを表現することは難しいです。とはいえ、必要であれば切り出して書けばよいですし、アソシエーションが複雑になる前にスピーディーにテストデータを作りたい場合はぜひ。