use*_*003 12 ruby rspec ruby-on-rails factory-bot
我有一个应用程序,用户可以使用多种服务登录,例如Google Plus,Facebook,Twitter等.
为了方便这一点,我有一个记录的基础User
模型has_many
Identity
.
Identity
记录都有一个provider
字段(例如"Google"
,"Facebook"
等等),以指示用于登录的提供程序."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
Run Code Online (Sandbox Code Playgroud)
该User
模型具有创建Identity
记录的回调.它运行时效果很好
user = FactoryGirl.create(:user)
Run Code Online (Sandbox Code Playgroud)
但是,如果我创造identity
,而不是
identity = FactoryGirl.create(:identity)
Run Code Online (Sandbox Code Playgroud)
该identity
工厂将首先尝试创建父user
,这将反过来创建另一个identity
.当它最终回到创建identity
我进行调用时,另一个identity
已经存在并且相同provider
,user
并且它失败了.
从本质上讲,我需要一种方法让after(:create)
回调user
在:identity
工厂创建时不会触发.有没有办法告诉是什么让这个电话创建了一个特定的工厂?
Fin*_*inn 10
正如Dave所指出的,使用瞬态属性是一种选择.另一种选择是nil
在构建相关工厂时通过.
让我用一个例子来说明:
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
Run Code Online (Sandbox Code Playgroud)
当我们调用时build(:user)
,代码最终到达以下行:
identity { build(:identity, user: nil) }
Run Code Online (Sandbox Code Playgroud)
这称为身份工厂.当它到达通常构建用户关联(user { build(:user, identity: nil) }
)的行时,它会跳过它,因为用户已经设置(为零).恭喜你,你只是避免了循环依赖!
当你打电话时,它的工作方式相同build(:identity)
.
还有一件事:在您的情况下,您需要访问身份工厂中用户的电子邮件属性.在您的代码示例中,您说:
factory :identity do
...
email { user.email }
end
Run Code Online (Sandbox Code Playgroud)
显然,build(:user)
当我们调用身份工厂时,因为我们将用户设置为nil ,所以这会失败.不要害怕!当我们调用身份工厂时,我们只是通过电子邮件传递一个新的用户对象.这条线变成:
identity { build(:identity, user: User.new(email: email)) }
Run Code Online (Sandbox Code Playgroud)
这将阻止循环,无限关联循环以及确保标识工厂中的电子邮件属性可用.
最后,您的代码将如下所示:
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
Run Code Online (Sandbox Code Playgroud)
希望它有用!
我认为没有一个好的方法可以让一个工厂在没有协作的情况下判断它已被另一个工厂调用。(您始终可以检查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
Run Code Online (Sandbox Code Playgroud)