David Geismar
David Geismar

Reputation: 3422

How do you create association in after_build in factory bot rails?

I have a data model where account has a unique validation on a subdomain.

Most of my other objects are associated with the account object.

In my data model, a product has an account_id and a program_id

In FactoryBot I would like to write something like :

create(:product, :with_program, account: account)

Then to have all associated resources created with this particular account.

and in my product factory :

FactoryBot.define do
  factory :product do
    association :account
    association :program
    trait :with_program do
      after_build do |product|
        product.program = create(
          :program,
          :with_author,
          account: product.account
        )
      end
    end
  end
end

However this directly creates the program which in turn recreates an account them triggers the uniqueness validation on account.

FactoryBot.define do
  factory :program do
    association :account
  end
end

So far the only solution I found is to create all associations product relies on before creating the actual product and passing them to the create method :

  let(:program) do
      create(
        :program,
        :with_future_start_date_sessions,
        account: account,
        author: author
      )
    end
create(
        :product,
        :draft,
        program: program,
        account: account
      )
    end

Is there a smarter way to pass down the account to all product associations and create them in a trait or something equivalent?

Upvotes: 1

Views: 3868

Answers (2)

bonyiii
bonyiii

Reputation: 2891

I'm not sure if this helps, but I did something like this. I have various validations on the associated models and this way both build and create factories worked:

FactoryBot.define do
  factory :product do
    account(:build) do |product|
      product.program ||= build(:program)
      product.account ||= build(:account)
    end

    after(:create) do |product|
      product.program.save!
      product.account.save!
    end
  end
end

Upvotes: 0

R. Sierra
R. Sierra

Reputation: 1205

When creating complex associations I always prefer to do it with a top down approach. Instead of creating products with account, try creating an account with products, that way you wouldn't need to create associations before hand.

FactoryBot.define do
  factory :account do
    # account attributes...

    trait :with_products do
      transient do
         product_count {2}
      end

      after(:build, :create) do |account, evaluator|
         create_list(
           :product,
           evaluator.product_count, 
           :with_program, 
           account: account
         )
      end
    end
  end
end

Then you could create products by calling

# 1 account, 5 products
FactoryBot::create(:account, :with_products, product_count: 5)
# 5 accounts with 1 product each
FactoryBot::create_list(:account, 5, :with_products, product_count: 1)

Upvotes: 3

Related Questions