如何避免在 factory_girl 中循环创建关联模型?
How to avoid circular creation of associated models in factory_girl?
我有一个应用程序,用户可以在其中使用多种服务登录,例如Google 另外,脸书、推特等
为方便起见,我有一个基础 User
模型,其中 has_many
Identity
记录。
- 每个
Identity
记录都有一个 provider
字段(例如 "Google"
、"Facebook"
等),以指示用于登录的提供商。
- 有一个 ActiveRecord 验证只允许用户拥有每种类型的提供程序之一。所以一个用户不能有 2
"Google"
identities
.
我的工厂设置如下:
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
模型有一个创建 Identity
记录的回调。当 运行
时效果很好
user = FactoryGirl.create(:user)
但是,如果我改为创建 identity
identity = FactoryGirl.create(:identity)
identity
工厂将首先尝试创建一个父 user
,这将依次创建另一个 identity
。当它最终回到创建我调用的 identity
时,另一个 identity
已经存在,并且 user
具有相同的 provider
并且它失败了。
本质上,我需要一种方法让 after(:create)
回调在 :identity
工厂创建 user
时不触发。有没有办法知道是什么调用创建了一个特定的工厂?
我不认为有什么好的方法可以让工厂知道它是在没有协作的情况下被另一个工厂调用的。 (您始终可以检查 caller_locations
,但这并不好。)相反,让一个工厂使用瞬态属性告诉另一个工厂表现不同:
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:避免circular/infinite 关联之间的循环
让我举个例子:
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) }
) 时,它会跳过它,因为用户已经被设置(为零)。恭喜,您刚刚避免了循环依赖!
当您调用 build(:identity)
时,它的工作方式相同。
FactoryGirl:从关联工厂中的一个工厂访问属性
还有最后一件事:在您的情况下,您需要访问身份工厂中用户的电子邮件属性。在您的代码示例中,您说:
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
希望对您有所帮助!
我有一个应用程序,用户可以在其中使用多种服务登录,例如Google 另外,脸书、推特等
为方便起见,我有一个基础 User
模型,其中 has_many
Identity
记录。
- 每个
Identity
记录都有一个provider
字段(例如"Google"
、"Facebook"
等),以指示用于登录的提供商。 - 有一个 ActiveRecord 验证只允许用户拥有每种类型的提供程序之一。所以一个用户不能有 2
"Google"
identities
.
我的工厂设置如下:
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
模型有一个创建 Identity
记录的回调。当 运行
user = FactoryGirl.create(:user)
但是,如果我改为创建 identity
identity = FactoryGirl.create(:identity)
identity
工厂将首先尝试创建一个父 user
,这将依次创建另一个 identity
。当它最终回到创建我调用的 identity
时,另一个 identity
已经存在,并且 user
具有相同的 provider
并且它失败了。
本质上,我需要一种方法让 after(:create)
回调在 :identity
工厂创建 user
时不触发。有没有办法知道是什么调用创建了一个特定的工厂?
我不认为有什么好的方法可以让工厂知道它是在没有协作的情况下被另一个工厂调用的。 (您始终可以检查 caller_locations
,但这并不好。)相反,让一个工厂使用瞬态属性告诉另一个工厂表现不同:
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:避免circular/infinite 关联之间的循环
让我举个例子:
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) }
) 时,它会跳过它,因为用户已经被设置(为零)。恭喜,您刚刚避免了循环依赖!
当您调用 build(:identity)
时,它的工作方式相同。
FactoryGirl:从关联工厂中的一个工厂访问属性
还有最后一件事:在您的情况下,您需要访问身份工厂中用户的电子邮件属性。在您的代码示例中,您说:
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
希望对您有所帮助!