Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FactoryGirl define attribute by calling method on another factory

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.

like image 242
aceofbassgreg Avatar asked Apr 14 '13 13:04

aceofbassgreg


People also ask

How to add a set attribute to the article factory?

If you need to add a set attribute to the factory, you can add it as an alternate state of the factory-using trait. In the example used above, you can add a trait to create an alternate state of the article factory that includes the article publish date. You also need to add a test for the alternate state. You can use it as follows:

What is the data factory pattern?

This is where the data factory pattern steps into test-driven development. Data Factory (or factory in short) is a blueprint that allows us to create an object, or a collection of objects, with predefined sets of values. You can have multiple sets of predefined values for a single factory.

Can I have multiple predefined values for a single factory?

You can have multiple sets of predefined values for a single factory. In fact, you should create them for each use case of the factory in order to be able to test all use cases of a given method, object or feature.

Can I use factorybot in a Ruby on Rails application?

FactoryBot was built using the Ruby programming language. However, you can use it for multiple frameworks, such as Ruby on Rails, Sinatra, and Padrino. This article covers the implementation of FactoryBot in a Ruby on Rails (i.e. Rails) application.


1 Answers

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
like image 82
mguymon Avatar answered Oct 17 '22 01:10

mguymon