How to reference associated records with Factorybot?

192 Views Asked by At

I have a ruby on rails application with the following models.

class User < ApplicationRecord
  has_many :crews, dependent: :destroy
end
class Crew < ApplicationRecord
  belongs_to :user
end

So every time I want to create a crew model it must reference a user model or it throws an error when trying to save.

I'm using FactoryBot to build all my test models in my unit tests and I'd like to be able to provision both my user model and the dependent crew model at the same time, but I need to be able to then reference the crew model.

For instance, I have the following factory for user.

FactoryBot.define do
  factory :user do
    email { '[email protected]' }

    factory :user_with_crew do
      after(:create) do |user|
        create(:crew, user: user)
      end
    end
  end
end

I can invoke it in my specs like this.

let(:user) { create :user_with_crew }

but then I have no reference to the crew model which I need to do my testing. I could do some manual creation and assignment in my before block, but I'm using FactoryBot to try to keep my code DRY and I'm sure someone has had to do this before. Any idea on how to accomplish this?

1

There are 1 best solutions below

0
Diego Thomaz On

If I understood this correctly I'd implement this in a different way, a simpler way, actually. You said "every time you want to create a crew" but you're using the user factory. I'd suggest using a factory for the crew model, so you'd have something like this:

FactoryBot.define do
  factory :crew do
    association :user
  end
end

With this factory, you can pass the user as an argument:

let(:user) { create(:user) }
let(:crew) { create(:crew, user: user }

But, if for some reason you still want to use the user factory, you can use transient block to receive the crew as a param. So you'd have something like this:

FactoryBot.define do
  factory :user_with_crew do
    transient do
      crew { build(:crew) }
    end
    after(:create) do |user, evaluator|
      evaluator.crew.user_id = user.id
      evaluator.crew.save
    end
  end
end

With the code above, if you don't provide the crew, it'll build one for you, however, if you provide one, then the factory will use it instead.