FactoryGirl define attribute by calling method on another factory
Asked Answered
B

1

11

Here is an example from the FactoryGirl documentation:

FactoryGirl.define do
  factory :post do
    name "Post name"
    user
  end
end

In this example, user is invoking another factory. What I would like to do is effectively call user.id, but to set it as the definition of an attribute. Here's a stripped-down example:

**models/job.rb**
  ...
  belongs_to :assignee, :class_name => "User"
  belongs_to :user
  ...
  attr_accessible :assignee_id, :user_id
  ...
end


**factories/jobs.rb**
FactoryGirl.define do
  factory :job do
    assignee_id user.id      #what I would like to do, but triggers "undefined method 'id'" error
    user_id user.id          #user_id is an attribute of the model and is the job assignor
 end

I've tried to incorporate the part of the documentation that discusses aliases, but with no luck:

FactoryGirl.define do
  factory :user, :aliases => [:assignee] do
  ....

I feel like (hope?) I'm close here, but any insight is appreciated. Thanks.

EDIT: This code gets my specs running!

**factories/jobs.rb**

FactoryGirl.define do
  factory :job do
    before(:create) do |job|
      user = FactoryGirl.create(:user)
      job.assignee = user
      job.user  = user
    end

  association :assignee, factory: :user
  association :user, factory: :user
  sequence(:user_id) { |n| n }
  sequence(:assignee_id) { |n| n }
  ...
end

And it passes my it { should be_valid } spec, so it seems that the factory is fine, though I think I have some refactoring in the spec itself when I'm calling FactoryGirl.create.

The code above incorporates the suggestions from mguymon. Thanks!

FINAL UPDATE

After going back and re-reading Hartl's discussion on model associations, I was able to put this matter to rest. What I have above was techincally valid, but didn't actually pass the attributes in properly when i built or created jobs in my spec. Here's what I should have had:

FactoryGirl.define do
  factory :job do
    association :assignee, factory: :user
    user
  end
end

My problem also stemmed from how I was creating factories in my spec, so here's how I should have been doing it (but wasn't...sigh):

    let(:user) { create(:user) }
    before { @job = create(:job, user: @user) }

It seems that I don't explicitly have to have association :user in my factory, nor do I need the before block from above.

As an aside, I also learned that I can debug by including puts @job within an expect statement, or call @job.assignee_id to make sure that the attributes are being loaded properly. When that particular spec is run, the puts statement will output right by the F or . from the spec.

Bicyclic answered 14/4, 2013 at 13:35 Comment(1)
sorry, but can you show the exception stack trace? thanksMagnetron
H
18

For the latest version of FactoryGirl, use association to map to other ActiveRecord Models:

factory :job do
  # ...
  association :assignee, factory: :user
end

This is straight from the docs.


From the error you posted, it is stating you are trying to get the user.id but user is not an ActiveRecord instance but a proxy from FactoryGirl. You will not get this error if you are using the association method. If you need to directly access a model, you have to manually build it first. You can do this by passing a block in your factory:

factory :job do
    assignee_id { FactoryGirl.create(:user).id }
end

It looks like you are trying to associate the same model twice, for this you can use before_create callback to create a User model and assign to user and assignee:

factory :job do

  before(:create) do |job|
      user = FactoryGirl.create(:user)
      job.assignee = user
      job.user = user
  end
end
Hellgrammite answered 14/4, 2013 at 13:42 Comment(3)
Thanks for the answer and link. Even with the association (which I'm sure is need), I'm still getting an "undefined method 'id'" error.Bicyclic
Your edit did the trick. Now that my specs finally run, I need to go through and fix some troublesome failures caused by duplicate users...Bicyclic
It turns out I didn't need `before(:create), nor did I need to create a user from the Factory. Though my factory was testing as valid, it wasn't properly loading the attributes into the jobs I was creating or building in my specs (I verified this by using puts @job within specs). I've added the correct code (or, at least a better version...maybe there is a 'more correct' version) to my question, but I'm leaving your answer as the checked answer since I explicitly needed to associate the assignee with the user and wouldn't of got that without your help.Bicyclic

© 2022 - 2024 — McMap. All rights reserved.