Ruby on rails 如何避免在factory中循环创建关联模型?

Ruby on rails 如何避免在factory中循环创建关联模型?,ruby-on-rails,ruby,rspec,factory-bot,Ruby On Rails,Ruby,Rspec,Factory Bot,我有一个应用程序,用户可以使用多种服务登录,例如Google Plus、Facebook、Twitter等 为了方便这一点,我有一个基本的用户模型,有许多身份记录 每个标识记录都有一个提供者字段(例如“Google”,“Facebook”,等等),以指示使用哪个提供者登录 ActiveRecord验证只允许用户拥有每种类型的提供者中的一种。因此,用户不能拥有2个“谷歌”身份 我设立工厂的方式如下: FactoryGirl.define do factory :user do se

我有一个应用程序,用户可以使用多种服务登录,例如Google Plus、Facebook、Twitter等

为了方便这一点,我有一个基本的
用户
模型,
有许多
身份
记录

  • 每个
    标识
    记录都有一个
    提供者
    字段(例如
    “Google”
    “Facebook”
    ,等等),以指示使用哪个提供者登录
  • ActiveRecord验证只允许用户拥有每种类型的提供者中的一种。因此,用户不能拥有2个
    “谷歌”
    身份
我设立工厂的方式如下:

FactoryGirl.define do
  factory :user do
    sequence(:name) { |n| "Julio Jones-#{n}"}
    sequence(:email) { |n| "julio.jones-#{n}@atl.com" }

    after(:create) do |user|
      create(:identity, user: user)
    end
  end

  factory :identity do
    user

    provider "Google"
    email { user.email }
    password "password"
  end
end
用户
模型有一个回调,用于创建一个
标识
记录。跑步时效果很好

user = FactoryGirl.create(:user)
但是,如果我创建
标识

identity = FactoryGirl.create(:identity)
标识
工厂将首先尝试创建一个父
用户
,这将反过来创建另一个
标识
。当它最终返回到创建我调用的
标识
时,另一个
标识
已经存在,并且该
用户的
提供者
相同,但失败了


本质上,我需要一种方法,使(:create)
之后的
回调在
用户
:identity
工厂创建时不触发。有没有一种方法可以告诉我们是什么原因导致创建了一个特定的工厂?

我认为对于一个工厂来说,没有一种好方法可以告诉我们它是被另一个没有合作的人调用的。(您始终可以检查调用方位置,但这并不好。)相反,让一个工厂使用瞬态属性告诉另一个工厂不同的行为:

FactoryGirl.define do
  factory :user do
    transient do
      create_identity true
    end

    after(:create) do |user, evaluator|
      if evaluator.create_identity
        create(:identity, user: user)
      end
    end

  end

  factory :identity do
    association :user, factory: :user, create_identity: false
  end

end

正如Dave所指出的,使用瞬态属性是一种选择。另一个选项是在构建关联工厂时通过
nil

FactoryGirl:避免关联之间的循环/无限循环 让我举例说明:

FactoryGirl.define do
  factory :user do
    sequence(:name) { |n| "Julio Jones-#{n}"}
    sequence(:email) { |n| "julio.jones-#{n}@atl.com" }
    # we pass user: nil here because it will cause the identity factory
    # to just skip the line user { ... }.
    identity { build(:identity, user: nil) }
  end

  factory :identity do
    # we pass user: nil here because it will cause the user factory
    # to just skip the line idenitity { ... }.
    user { build(:user, identity: nil) }
    provider "Google"
    email "email@example.com"
    password "password"
  end
end 
当我们调用
build(:user)
时,代码最终到达以下行:

identity { build(:identity, user: nil) }
这称为身份工厂。当它到达通常构建用户关联的行(
user{build(:user,identity:nil)}
)时,它将跳过它,因为用户已经被设置为(nil)。恭喜你,你刚刚避免了循环依赖

当您调用
build(:identity)
时,它的工作方式相同


FactoryGirl:从关联工厂中的一个工厂访问属性 还有最后一件事:在您的情况下,您需要访问identity factory中用户的电子邮件属性。在代码示例中,您说:

factory :identity do
  ...
  email { user.email }
end
显然,当我们调用
build(:user)
时,这会失败,因为我们在调用标识工厂时将user设置为nil。不要害怕!当我们调用身份工厂时,我们只需通过电子邮件传递一个新的用户对象。因此,这条线变成:

identity { build(:identity, user: User.new(email: email)) }
这既可以防止循环无限关联循环,也可以确保电子邮件属性在标识工厂中可用

最后,您的代码如下所示:

FactoryGirl.define do
  factory :user do
    sequence(:name) { |n| "Julio Jones-#{n}"}
    sequence(:email) { |n| "julio.jones-#{n}@atl.com" }
    # we pass user: User.new here because it will...
    # a) cause the identity factory to skip the line user { ... } and
    # b) allow us to use the email attribute in the identity factory.
    identity { build(:identity, user: User.new(email: email)) }
  end

  factory :identity do
    # we pass user: nil here because it will cause the user factory
    # to just skip the line idenitity { ... }.
    user { build(:user, identity: nil) }
    provider "Google"
    email { user.email }
    password "password"
  end
end 

希望对你有帮助

谢谢!这可能是最直接、最可靠的解决方案