Найти или создать запись через ассоциацию factory_girl
У меня есть модель User, которая принадлежит группе. Группа должна иметь уникальный атрибут имени. Пользователь factory и группа factory определяются как:
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 |
| [email protected] | Name: mygroup |
и это может работать, только если ассоциация используется в определении factory.
Ответы
Ответ 1
В итоге я использовал сочетание методов, найденных вокруг сети, один из которых был унаследованными фабриками, предложенными 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
Вы можете использовать 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) }
вместо.
Ответ 3
Вы также можете использовать стратегию 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
Вы можете использовать эту суть.
А затем выполните следующее
FactoryGirl.define do
factory :group do
name "name"
end
factory :user do
association :group, factory: :group, strategy: :find_or_create, name: "name"
end
end
Это работает для меня, хотя.
Ответ 4
Обычно я просто делаю несколько определений factory. Один для пользователя с группой и один для безгруппового пользователя:
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 вы можете использовать их в своих определениях шагов, чтобы создавать пользователей и группы отдельно и объединять их по желанию. Например, вы можете создать одного сгруппированного пользователя и одного одиночного пользователя и присоединить одинокого пользователя к группе сгруппированных пользователей.
В любом случае, вы должны взглянуть на драгоценный камень сокета, который позволит вам писать такие шаги, как:
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....
Ответ 5
Я использую именно тот сценарий, который вы описали в своем вопросе:
Given the following user exists:
| Email | Group |
| [email protected] | Name: mygroup |
Вы можете расширить его, как:
Given the following user exists:
| Email | Group |
| [email protected] | Name: mygroup |
| [email protected] | Name: mygroup |
| [email protected] | Name: mygroup |
Это создаст 3 пользователя с группой "mygroup". Поскольку он используется так, как это использует функцию "find_or_create_by", первый вызов создает группу, следующие два вызова обнаруживают уже созданную группу.
Ответ 6
Я столкнулся с подобной проблемой в последнее время, и вот что я пытался.
Чтобы 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
переопределяется для извлечения существующей записи, если она существует, вместо попытки всегда создавать новую запись.
Я написал статью, объясняющую это.
Ответ 7
У меня была похожая проблема, и я нашел это решение. Он ищет группу по имени и, если он найден, связывает пользователя с этой группой. В противном случае он создает группу с таким именем и затем связывается с ней.
factory :user do
group { Group.find_by(name: 'unique_name') || FactoryBot.create(:group, name: 'unique_name') }
end
Я надеюсь, что это может быть полезным для кого-то :)