FactoryGirl覆盖关联对象的属性

29

这可能很简单,但我无法在任何地方找到示例。

我有两个工厂:

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

我想做的是在创建个人资料时覆盖一些用户属性:

p = FactoryGirl.create(:profile, user: {email: "test@test.com"})

或类似的东西,但我无法正确使用语法。错误:

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

我知道我可以先创建用户,然后将其与配置文件关联起来来完成此操作,但我认为必须有更好的方法。

或者这样也可以:

p = FactoryGirl.create(:profile, user: FactoryGirl.create(:user, email: "test@test.com"))

但这似乎过于复杂。有没有更简单的方式来覆盖相关属性? 正确的语法是什么?

3个回答

25

根据FactoryGirl的一位创建者所说,你不能将动态参数传递给关联助手 (Pass parameter in setting attribute on association in FactoryGirl)。

但是,你应该能够做到这样:

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

那么你可以几乎按照你想要的方式来调用它:

p = FactoryGirl.create(:profile, user_args: {email: "test@test.com"})

2
很棒的回答。您能否更新以符合最新的Rails版本?例如,当我实现这个答案时,我会收到“DEPRECATION WARNING:#ignore已过时,并将在5.0中删除。” - Cole Bittel
1
你可以使用“瞬时”的代替“忽略”,以消除警告。 - Jarod Adair

6
我认为你可以通过回调函数和瞬态属性来使这个工作。如果你像下面这样修改你的档案工厂:
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

那么你应该可以像这样调用它并获得期望的结果:
p = FactoryGirl.create(:profile, user_email: "test@test.com")

不过,我还没有测试过。


谢谢,但我希望它适用于任何属性,所以我不想为每个属性都编写代码。也许没有其他人需要这个... - bobomoreno
2
我认为你的示例有错误。将 after(:create) 更改为 profile.user.email = evaluator.user_email unless evaluator.user_email.nil? - Kelly

3
通过先创建用户,然后创建个人资料,解决了这个问题:
my_user = FactoryGirl.create(:user, user_email: "test@test.com")
my_profile = FactoryGirl.create(:profile, user: my_user.id)

所以,这与问题中的几乎相同,只是跨越两行。 唯一的区别是明确访问了".id"。 在Rails 5上进行了测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接