今回はFactoryBotでtraitを使って重複をなくす方法について書いていきます。
traitを使わない場合
FactoryBot.define do factory :todo_task do title { 'Task' } status { :todo } # enumを使っている association :project end factory :doing_task do title { 'Task' } status { :doing } # enumを使っている association :project end factory :done_task do title { 'Task' } status { :done } # enumを使っている completion_date { Time.current.yesterday } association :project end end
Factory Bot では同じデータを作成するファクトリを複数定義することもできます。
上記のように todo_task、doing_task、done_taskの3つのファクトリを作成しています。
このように複数定義できます。
3つの違いは
- TODOとして登録しているtodo_task
- 今取り組んでいるdoing_task
- 終了したdone_task
の3つに分けています。
また、done_taskは終了したTaskなので、終了した日付のデータを持つようにcompletion_date { Time.current.yesterday }
と記載しています。
FactoryBot.create(:todo_task) FactoryBot.create(:doing_task) FactoryBot.create(:done_task)
もしこのFactoryBotを呼び出そうと思ったら、上記のようにcreate
やbuild
メソッドの引数にFactoryBotの名前を指定してあげることで、それぞれのFactoryBotを作成することができます。
require 'rails_helper' RSpec.describe 'Task', type: :system do let(:todo_task) { FactoryBot.create(:todo_task) } let(:todo_task) { FactoryBot.create(:doing_task) } let(:todo_task) { FactoryBot.create(:done_task) }
テストファイルの中でlet
やlet!
を使って書く場合は、上記のような感じになります。
require 'rails_helper' RSpec.describe 'Task', type: :system do let(:todo_task) { create(:todo_task) } let(:todo_task) { create(:doing_task) } let(:todo_task) { create(:done_task) }
さらにrails_helper.rb
にconfig.include FactoryBot::Syntax::Methods
という設定を書くことでFactoryBot.
という記載は必要なくなるので、上記のように省略して書くことができます。
FactoryBot.define do factory :todo_task do title { 'Task' } status { :todo } # enumを使っている association :project end factory :doing_task do title { 'Task' } status { :doing } # enumを使っている association :project end factory :done_task do title { 'Task' } status { :done } # enumを使っている completion_date { Time.current.yesterday } association :project end end
話を戻して、FactoryBotを見てみると、ファクトリにたくさん重複があります。
FactoryBot.define do factory :todo_task do title { 'Task' } status { :todo } association :project end factory :doing_task do title { 'Task' } status { :doing } association :project end factory :done_task do title { 'Task' } status { :done } completion_date { Time.current.yesterday } association :project end # 新しいファクトリ factory :〇〇_task do title { 'Task' } status { :△△△△ } association :project end end
もし、新しいファクトリを定義しようとした場合、全属性を再定義しなければいけません。
上記のように〇〇_task
を作って、title
やstatus
などを全部再定義しないといけません。
FactoryBot.define do factory :todo_task do title { 'Task' } # 追加 content { 'Content' } status { :todo } association :project end factory :doing_task do title { 'Task' } # 追加 content { 'Content' } status { :doing } association :project end factory :done_task do title { 'Task' } # 追加 content { 'Content' } status { :done } completion_date { Time.current.yesterday } association :project end # 新しいファクトリ factory :〇〇_task do title { 'Task' } # 追加 content { 'Content' } status { :△△△△ } association :project end end
また、ファクトリを全部再定義しないといけないということは、逆にいうとTaskモデルの属性(カラム)を変更した場合に毎回全部のファクトリの定義を変更する必要が出てきます。
例えばTaskモデルの属性にcontent
を追加して、その属性(カラム)がvalidates :content, presence: true
というバリデーションが付与されていたりした場合は、全てのファクトリに
content { 'Content' }
にように追加してあげる必要があります。
こんな時に、Factory Bot には重複を減らすテクニックがあります。
一つ目は 「ファクトリの継承 」を使ってユニークな属性だけを変えることです。
二つ目が 「trait」を使う方法です。
今回は「trait」を使う方法を記載します。
traitを使って重複を減らす方法
FactoryBot.define do factory :task do title { 'Task' } status { :todo } association :project # status が done trait :done do status { :done } completion_date { Time.current.yesterday } end # status が doing trait :doing do status { :doing } end end end
traitは上記のように書きます。
trait
というのがキーワードになります。
trait
を使うことで重複がなくなっていることがわかります。
デフォルトから変更したい部分だけをtraitで記載することで重複を減らすことができます。
# taskファクトリーを使う FactoryBot.create(:task) # taskファクトリー と trait :done を使う FactoryBot.create(:task, :done) # taskファクトリー と trait :doing を使う FactoryBot.create(:task, :doing)
FactoryBot.create(:task)
でFactoryBot
を呼び出してあげると、task
という名前がついているFactoryBot
を使ってデータを作ることができます。
FactoryBot.create(:task, :done)
でFactoryBot
を呼び出してあげると、task
という名前のついてFactoryBotとdone
という名前のついたtraitを使ってデータを作ることができます。
実際に確認してみるとFactoryBot.create(:task)
でFactoryBot
を呼んであげると、titleは「Task」でstatusは「todo」になっていることがわかります。
FactoryBot.create(:task, :done)
でFactoryBot
を呼んであげると、statusは「done」でcompletion_dateにも日付が入っていることがわかります。
require 'rails_helper' RSpec.describe 'Task', type: :system do let(:task) { create(:task) } let(:done_task) { create(:task, :done) } let(:doing_task) { create(:task, :doing) }
let
を使って上記のように書いておいて、呼び出すこともできます。