Factory Girl error with has_many relationship
Asked Answered
Y

2

8

I have the following factories:

Factory.define :email do |email|
  email.email {"infomcburney.cowan.com"}
end

Factory.define :lead do |lead|
  lead.emails {|emails| [emails.association(:email)]}
end

Which are modeling the following classes

class Lead < ActiveRecord::Base
  has_many :emails
end

class Email < ActiveRecord::Base
  belongs_to :lead, :class_name => "Lead", :foreign_key => "lead_id"
end

When I run the this test through shoulda:

    should "capture emails" do
      lead = Factory.build(:lead)
      assert_equal(1, lead.emails.size)
    end

I get the following error:

Factory::AttributeDefinitionError: Attribute already defined: emails

I am completely stuck on this, can anyone point me in the right direction. I am using factory_girl 1.3.2.

Yandell answered 28/10, 2010 at 22:58 Comment(1)
Point of style: factories should contain placeholder data and nothing else. All that should matter to you when using them is that a valid lead is generated, and you should assume that the actual number of emails attached to the lead is subject to change. If your test depends on a certain number of leads, build the lead from the factory, then set the e-mails explicitly.Hydracid
S
9

I would recommend against adding has_many relationship data to your factories. The reason for this is that your lead factory now depends on populating this association and it's adding more coupling and potentially some confusion down the road if the association changes.

If you want to test this relationship (and I recommend you do), there's a great gem called Shoulda that adds unit test macros to ensure that the relationships are setup right. I haven't used it with the built in Rails Test::Unit, but an RSpec example would look something like:

describe Lead do
  it { should have_many(:emails) }
end

If you really want to test this relationship, you should do it in the spec. Remove the emails association from your lead factory and create a lead object and try to pass it a few email objects like so:

lead = Factory.build(:lead)
2.times do { lead.emails << Factory.build(:email, :lead => lead) }

Then it should have a couple emails association with it. However, you should put some faith in ActiveRecord and just test things that are above and beyond what Rails is already doing for you. This is where Shoulda comes in.

Another comment I have is on your Email belongs_to relationship. Since you're just using the default conventions, rails will know what to do.

class Email < ActiveRecord::Base
  belongs_to :lead
end
Skill answered 29/10, 2010 at 0:3 Comment(2)
Thank you for your reply but what if I want a factory called lead_with_one_email because I am always having to create it like you are doing above? Surely it makes sense to be able to do that it in the factory to cut down on repeating the creation code? It seems like this should be possible?Yandell
If all your lead tests are requiring at least one email, then this is an issue. You should be able to test your lead model independently from any other models. You might need to use mocks and stubs for this. However, if you just have a subset of tests that you want to test with an email, I would create a group isolating this behavior. In Rspec, this group is called either a describe or context block and it isolates a few tests that require a specific setup (such as an email).Skill
S
3

This is an interesting article which could be helpful:

http://icelab.com.au/articles/factorygirl-and-has-many-associations/

    FactoryGirl.define do
      factory :venue_with_gigs, :parent => :venue do
        after_create do |venue|
          FactoryGirl.create(:gig, :venue => venue)
        end
      end
    end
Samphire answered 25/6, 2012 at 14:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.