WeBlog

Webに関する情報を中心に発信してるブログ

FactoryBotでtraitを使って重複をなくす方法

今回は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_taskdone_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を呼び出そうと思ったら、上記のようにcreatebuildメソッドの引数に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) }

テストファイルの中でletlet!を使って書く場合は、上記のような感じになります。

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.rbconfig.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を作って、titlestatusなどを全部再定義しないといけません。

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を使ってデータを作ることができます。

f:id:weblog_tec:20210603141444p:plain

実際に確認してみるとFactoryBot.create(:task)FactoryBotを呼んであげると、titleは「Task」でstatusは「todo」になっていることがわかります。

f:id:weblog_tec:20210603141458p:plain

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を使って上記のように書いておいて、呼び出すこともできます。