ruby 通过 factory_girl 协会查找或创建记录
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7145256/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Find or create record through factory_girl association
提问by Slobodan Kovacevic
I have a User model that belongs to a Group. Group must have unique name attribute. User factory and group factory are defined as:
我有一个属于组的用户模型。组必须具有唯一的名称属性。用户工厂和组工厂定义为:
Factory.define :user do |f|
f.association :group, :factory => :group
# ...
end
Factory.define :group do |f|
f.name "default"
end
When the first user is created a new group is created too. When I try to create a second user it fails because it wants to create same group again.
创建第一个用户时,也会创建一个新组。当我尝试创建第二个用户时它失败了,因为它想再次创建相同的组。
Is there a way to tell factory_girl association method to look first for an existing record?
有没有办法告诉 factory_girl 关联方法首先查找现有记录?
Note: I did try to define a method to handle this, but then I cannot use f.association. I would like to be able to use it in Cucumber scenarios like this:
注意:我确实尝试定义一个方法来处理这个问题,但后来我不能使用 f.association。我希望能够在这样的 Cucumber 场景中使用它:
Given the following user exists:
| Email | Group |
| [email protected] | Name: mygroup |
and this can only work if association is used in Factory definition.
这只有在工厂定义中使用关联时才有效。
采纳答案by Slobodan Kovacevic
I ended up using a mix of methods found around the net, one of them being inherited factories as suggested by duckyfuzz in another answer.
我最终使用了在网络上找到的混合方法,其中一个是由duckyfuzz 在另一个答案中建议的继承工厂。
I did following:
我做了以下:
# in groups.rb factory
def get_group_named(name)
# get existing group or create new one
Group.where(:name => name).first || Factory(:group, :name => name)
end
Factory.define :group do |f|
f.name "default"
end
# in users.rb factory
Factory.define :user_in_whatever do |f|
f.group { |user| get_group_named("whatever") }
end
回答by Fernando Almeida
You can to use initialize_withwith find_or_createmethod
您可以使用initialize_withwithfind_or_create方法
FactoryGirl.define do
factory :group do
name "name"
initialize_with { Group.find_or_create_by_name(name)}
end
factory :user do
association :group
end
end
It can also be used with id
它也可以与 id 一起使用
FactoryGirl.define do
factory :group do
id 1
attr_1 "default"
attr_2 "default"
...
attr_n "default"
initialize_with { Group.find_or_create_by_id(id)}
end
factory :user do
association :group
end
end
For Rails 4
对于 Rails 4
The correct way in Rails 4 is Group.find_or_create_by(name: name), so you'd use
Rails 4 中的正确方法是Group.find_or_create_by(name: name),所以你会使用
initialize_with { Group.find_or_create_by(name: name) }
instead.
反而。
回答by Hiasinho
You can also use a FactoryGirl strategy to achieve this
您还可以使用 FactoryGirl 策略来实现此目的
module FactoryGirl
module Strategy
class Find
def association(runner)
runner.run
end
def result(evaluation)
build_class(evaluation).where(get_overrides(evaluation)).first
end
private
def build_class(evaluation)
evaluation.instance_variable_get(:@attribute_assigner).instance_variable_get(:@build_class)
end
def get_overrides(evaluation = nil)
return @overrides unless @overrides.nil?
evaluation.instance_variable_get(:@attribute_assigner).instance_variable_get(:@evaluator).instance_variable_get(:@overrides).clone
end
end
class FindOrCreate
def initialize
@strategy = FactoryGirl.strategy_by_name(:find).new
end
delegate :association, to: :@strategy
def result(evaluation)
found_object = @strategy.result(evaluation)
if found_object.nil?
@strategy = FactoryGirl.strategy_by_name(:create).new
@strategy.result(evaluation)
else
found_object
end
end
end
end
register_strategy(:find, Strategy::Find)
register_strategy(:find_or_create, Strategy::FindOrCreate)
end
You can use this gist. And then do the following
你可以使用这个 gist。然后执行以下操作
FactoryGirl.define do
factory :group do
name "name"
end
factory :user do
association :group, factory: :group, strategy: :find_or_create, name: "name"
end
end
This is working for me, though.
不过,这对我有用。
回答by David Tuite
Usually I just make multiple factory definitions. One for a user with a group and one for a groupless user:
通常我只是做多个工厂定义。一个用于有组的用户,另一个用于无组的用户:
Factory.define :user do |u|
u.email "email"
# other attributes
end
Factory.define :grouped_user, :parent => :user do |u|
u.association :group
# this will inherit the attributes of :user
end
THen you can use these in your step definitions to create users and groups seperatly and join them together at will. For example you could create one grouped user and one lone user and join the lone user to the grouped users team.
然后,您可以在步骤定义中使用这些来单独创建用户和组,并随意将它们连接在一起。例如,您可以创建一个分组用户和一个单独用户,并将单独用户加入分组用户团队。
Anyway, you should take a look at the pickle gemwhich will allow you to write steps like:
无论如何,你应该看看pickle gem,它允许你编写如下步骤:
Given a user exists with email: "[email protected]"
And a group exists with name: "default"
And the user: "[email protected]" has joined that group
When somethings happens....
回答by Joey Cheng
I faced a similar issue lately, and here's what I tried.
我最近遇到了类似的问题,这就是我尝试过的。
To ensure FactoryBot's buildand createstill behaves as it should, we should only override the logic of create, by doing:
为确保 FactoryBot 的build和create仍然按其应有的方式运行,我们应该仅create通过执行以下操作来覆盖 的逻辑:
factory :user do
association :group, factory: :group
# ...
end
factory :group do
to_create do |instance|
instance.attributes = Group.find_or_create_by(name: instance.name).attributes
instance.reload
end
name { "default" }
end
This ensures buildmaintains it's default behavior of "building/initializing the object" and does not perform any database read or write so it's always fast. Only logic of createis overridden to fetch an existing record if exists, instead of attempting to always create a new record.
这确保build保持它的“构建/初始化对象”的默认行为,并且不执行任何数据库读取或写入,因此它总是很快。create如果存在,则仅覆盖 的逻辑以获取现有记录,而不是尝试始终创建新记录。
I wrote an articleexplaining this.
我写了一篇文章来解释这个。
回答by L.Youl
I had a similar problem and came up with this solution. It looks for a group by name and if it is found it associates the user with that group. Otherwise it creates a group by that name and then associates with it.
我遇到了类似的问题,并提出了这个解决方案。它按名称查找组,如果找到,则将用户与该组相关联。否则,它会根据该名称创建一个组,然后与之关联。
factory :user do
group { Group.find_by(name: 'unique_name') || FactoryBot.create(:group, name: 'unique_name') }
end
I hope this can be useful to someone :)
我希望这对某人有用:)
回答by Jonas Bang Christensen
I'm using exactly the Cucumber scenario you described in your question:
我正在使用您在问题中描述的 Cucumber 场景:
Given the following user exists:
| Email | Group |
| [email protected] | Name: mygroup |
You can extend it like:
你可以像这样扩展它:
Given the following user exists:
| Email | Group |
| [email protected] | Name: mygroup |
| [email protected] | Name: mygroup |
| [email protected] | Name: mygroup |
This will create 3 users with the group "mygroup". As it used like this uses 'find_or_create_by' functionality, the first call creates the group, the next two calls finds the already created group.
这将使用组“mygroup”创建 3 个用户。因为它像这样使用'find_or_create_by'功能,第一个调用创建组,接下来的两个调用找到已经创建的组。

