transient

     describe '検索機能' do

      let(:article_with_author) { create(:article, :with_author, author_name: '伊藤') }
      let(:article_with_another_author) { create(:article, :with_author, author_name: '鈴木') }

       it '著者名で絞り込み検索ができること' do
        article_with_author
        article_with_another_author
        visit admin_articles_path
        within 'select[name="q[author_id]"]' do
          select '伊藤'
        end
        click_button '検索'
        expect(page).to have_content(article_with_author.title), '著者名での検索ができていません'
        expect(page).not_to have_content(article_with_another_author.title), '著者名での絞り込みができていません'
      end
 FactoryBot.define do
  factory :article do
    sequence(:title) { |n| "title-#{n}" }
    sequence(:slug) { |n| "slug-#{n}" }
    category
  end

  trait :with_author do
    transient do
      sequence(:author_name) { |n| "test_author_name_#{n}" }
      sequence(:tag_slug) { |n| "test_author_slug_#{n}" }
    end

    after(:build) do |article, evaluator|
      article.author = build(:author, name: evaluator.author_name, slug: evaluator.tag_slug)
    end
  end

Article(記事)モデルとAuthor(著者)モデルの関係性は多:1です。Authorが複数の記事を持つということです。

 within 'select[name="q[author_id]"]' do
 select '伊藤'

この部分は、セレクトボックスから伊藤という著書名を選ぶということです。



trait

あるfactoryの特定の状態に名前を付けて、付け外しできるようにする、というのが主な使い方になります。例えば、あるfactoryをある時はadminある時は非adminで作りたい時等に有効です。



transient

本来のクラスには存在しないが、Factoryでのみ使用したい一時的な変数を定義する。 そこで定義されたものは実際のmodelにはセットされないしattributes_forでも出力されない。 何のために使うかというと作成時に挙動を変更するためのフラグや追加データとして利用するのが一般的。

ex)author_nameとtag_slugという実際に作成するデータと直接関係ない新しい変数を定義している。

   trait :with_author do
    transient do
      sequence(:author_name) { |n| "test_author_name_#{n}" }
      sequence(:tag_slug) { |n| "test_author_slug_#{n}" }
    end

    after(:build) do |article, evaluator|
      article.author = build(:author, name: evaluator.author_name, slug: evaluator.tag_slug)
    end
  end

author_nameとtag_slugがユニークにならないようsequenceを入れる。

after(:build)によって生成したインスタンスがbuildされた直後に、evaluatorこの値がarticle.authorというAuthorモデルの作成時に渡されています。

生成したインスタンスがbuildされた直後に自由にインスタンスを修正することができる。コールバックみたいな感じ。第二関数にevaluatorを渡すことで、transientブロック内の変数にアクセスすることができる。



let

     describe '検索機能' do

      let(:article_with_author) { create(:article, :with_author, author_name: '伊藤') }

letでtraitの:with_authorを指定すると、同時にtraitのブロック内に存在するtransient内の値が取得できます。

:with_author でauthor_nameとtag_slugを作成。今回author_nameは指定されている伊藤になる。

本来であれば、アソシエーション関係のあるFactoryBotの呼び出しをletのみで行う場合、letを2回使う必要がありました。今回の場合、記事が1つとその記事の著者が1つです。しかし、transientを使用した場合、letの1回の呼び出しで2つのFactoryBotを作成しています!