我有一个属于某个组的用户模型。组必须具有唯一的名称属性。用户工厂和组工厂定义为:
Factory.define :user do |f|
f.association :group, :factory => :group
# ...
end
Factory.define :group do |f|
f.name "default"
end
创建第一个用户时,也会创建一个新组。当我尝试创建第二个用户时,它会失败,因为它想要再次创建相同的组。
有没有办法告诉factory_girl关联方法首先查看现有记录?
注意:我确实尝试定义了一个方法来处理这个,但后来我不能使用f.association。我希望能够在像这样的Cucumber场景中使用它:
Given the following user exists:
| Email | Group |
| test@email.com | Name: mygroup |
这只有在工厂定义中使用关联时才有效。
答案 0 :(得分:100)
您可以initialize_with
使用find_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
它也可以与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
对于Rails 4
Rails 4中的正确方法是Group.find_or_create_by(name: name)
,所以你要使用
initialize_with { Group.find_or_create_by(name: name) }
代替。
答案 1 :(得分:16)
我最终使用网络上发现的各种方法,其中一种是duckyfuzz在另一个答案中建议的继承工厂。
我做了以下事情:
# 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
答案 2 :(得分:6)
您还可以使用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
您可以使用this 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
但这对我有用。
答案 3 :(得分:2)
通常我只会制作多个工厂定义。一个用于具有组的用户,一个用于无组用户:
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
您可以在步骤定义中使用这些来分别创建用户和组,并随意将它们连接在一起。例如,您可以创建一个分组用户和一个单独用户,并将单个用户加入分组用户团队。
无论如何,您应该看一下pickle gem,它可以让您编写如下步骤:
Given a user exists with email: "hello@email.com"
And a group exists with name: "default"
And the user: "hello@gmail.com" has joined that group
When somethings happens....
答案 4 :(得分:0)
我正在使用你在问题中描述的Cucumber场景:
Given the following user exists:
| Email | Group |
| test@email.com | Name: mygroup |
您可以将其扩展为:
Given the following user exists:
| Email | Group |
| test@email.com | Name: mygroup |
| foo@email.com | Name: mygroup |
| bar@email.com | Name: mygroup |
这将使用“mygroup”组创建3个用户。正如它使用'find_or_create_by'功能一样,第一个调用创建组,接下来的两个调用查找已创建的组。
答案 5 :(得分:0)
我最近遇到了类似的问题,这就是我尝试过的事情。
为确保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
这可确保build
保持其默认的“构建/初始化对象”行为,并且不执行任何数据库读取或写入操作,因此始终保持快速状态。仅覆盖create
的逻辑以获取现有记录(如果存在),而不是尝试始终创建新记录。
我写了an article对此进行了解释。
答案 6 :(得分:0)
我遇到了类似的问题,并提出了解决方案。它按名称查找组,如果找到,则将用户与该组关联。否则,它将使用该名称创建一个组,然后将其与之关联。
factory :user do
group { Group.find_by(name: 'unique_name') || FactoryBot.create(:group, name: 'unique_name') }
end
我希望这对某人有用:)
答案 7 :(得分:0)
另一种方法(适用于任何属性和关联):
# config/initializers/factory_bot.rb
#
# Example use:
#
# factory :my_factory do
# change_factory_to_find_or_create
#
# some_attr { 7 }
# other_attr { "hello" }
# end
#
# FactoryBot.create(:my_factory) # creates
# FactoryBot.create(:my_factory) # finds
# FactoryBot.create(:my_factory, other_attr: "new value") # creates
# FactoryBot.create(:my_factory, other_attr: "new value") # finds
module FactoryBotEnhancements
def change_factory_to_find_or_create
to_create do |instance|
# Note that this will ignore nil value attributes, to avoid auto-generated attributes such as id and timestamps
attributes = instance.class.find_or_create_by(instance.attributes.compact).attributes
instance.attributes = attributes.except('id')
instance.id = attributes['id'] # id can't be mass-assigned
instance.instance_variable_set('@new_record', false) # marks record as persisted
end
end
end
# This makes the module available to all factory definition blocks
class FactoryBot::DefinitionProxy
include FactoryBotEnhancements
end
唯一的警告是你不能通过 nil 值找到。除此之外,它就像做梦一样