FactoryGirl override attribute of associated object
Asked Answered
F

3

29

This is probably silly simple but I can't find an example anywhere.

I have two factories:

FactoryGirl.define do
  factory :profile do
    user

    title "director"
    bio "I am very good at things"
    linked_in "http://my.linkedin.profile.com"
    website "www.mysite.com"
    city "London"
  end
end

FactoryGirl.define do 
  factory :user do |u|
    u.first_name {Faker::Name.first_name}
    u.last_name {Faker::Name.last_name}

    company 'National Stock Exchange'
    u.email {Faker::Internet.email}
  end
end

What I want to do is override some of the user attributes when I create a profile:

p = FactoryGirl.create(:profile, user: {email: "[email protected]"})

or something similar, but I can't get the syntax right. Error:

ActiveRecord::AssociationTypeMismatch: User(#70239688060520) expected, got Hash(#70239631338900)

I know I can do this by creating the user first and then associating it with the profile, but I thought there must be a better way.

Or this will work:

p = FactoryGirl.create(:profile, user: FactoryGirl.create(:user, email: "[email protected]"))

but this seems overly complex. Is there not a simpler way to override an associated attribute? What is the correct syntax for this??

Fango answered 30/4, 2013 at 10:11 Comment(0)
D
25

According to one of FactoryGirl's creators, you can't pass dynamic arguments to the association helper (Pass parameter in setting attribute on association in FactoryGirl).

However, you should be able to do something like this:

FactoryGirl.define do
  factory :profile do
    transient do
      user_args nil
    end
    user { build(:user, user_args) }

    after(:create) do |profile|
      profile.user.save!
    end
  end
end

Then you can call it almost like you wanted:

p = FactoryGirl.create(:profile, user_args: {email: "[email protected]"})
Donnetta answered 15/8, 2013 at 21:0 Comment(2)
Great answer. Would you update this to comply with the latest Rails versions. Eg. I'm getting a "DEPRECATION WARNING: #ignore is deprecated and will be removed in 5.0." when implementing this answer.Czernowitz
You can use "transient" instead of "ignore" to get rid of the warningKrp
A
6

I think you can make this work with callbacks and transient attributes. If you modify your profile factory like so:

FactoryGirl.define do
  factory :profile do
    user

    ignore do
      user_email nil  # by default, we'll use the value from the user factory
    end

    title "director"
    bio "I am very good at things"
    linked_in "http://my.linkedin.profile.com"
    website "www.mysite.com"
    city "London"

    after(:create) do |profile, evaluator|
      # update the user email if we specified a value in the invocation
      profile.user.email = evaluator.user_email unless evaluator.user_email.nil?
    end
  end
end

then you should be able to invoke it like this and get the desired result:

p = FactoryGirl.create(:profile, user_email: "[email protected]")

I haven't tested it, though.

Aerosol answered 30/4, 2013 at 14:2 Comment(2)
Thanks, but I would want that to work for any attribute, so I wouldn't want to have to code it for each like that. Maybe nobody else needs this...Fango
I think your example has an error. Change the after(:create) to be profile.user.email = evaluator.user_email unless evaluator.user_email.nil?Curlicue
H
3

Solved it by creating User first, and then Profile:

my_user = FactoryGirl.create(:user, user_email: "[email protected]")
my_profile = FactoryGirl.create(:profile, user: my_user.id)

So, this is almost the same as in the question, split across two lines. Only real difference is the explicit access to ".id". Tested with Rails 5.

Hirschfeld answered 7/10, 2016 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.