LoginSignup
4
3

More than 3 years have passed since last update.

factory_botでモデルのenum別のtraitを一発で書く小ネタ

Posted at

image.png

今回はRailsでテストデータを作成する”factroy_bot”に関する小ネタです。

image.png

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とする)で持ち判別する」モデルです。

app/models/user.rb
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が複雑になるのは当然のことですが...

spec/factories/user.rb
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ってすごい。

spec/factories/user.rb
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を表現することは難しいです。とはいえ、必要であれば切り出して書けばよいですし、アソシエーションが複雑になる前にスピーディーにテストデータを作りたい場合はぜひ。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3