Как мне создать фабрику для моделей, которые имеют отношение has_one / belongs_to с проверками, которые обычно преодолеваются вложенными атрибутами?

#ruby-on-rails #validation #factory-bot

#ruby-on-rails #проверка #factory-бот

Вопрос:

У меня есть модель учетной записи, которая имеет модель пользователя has_one, и модель пользователя, которая принадлежит модели учетной записи belongs_to. Я думаю, что базовый код, необходимый для демонстрации, это:

 class Account < ActiveRecord::Base
  has_one :user
  validates_presence_of :user
  accepts_nested_attributes_for :user
end

class User < ActiveRecord::Base
  belongs_to :account
  # validates_presence_of :account # this is not actually present,
                                   # but is implied by a not null requirement
                                   # in the database, so it only takes effect on
                                   # save or update, instead of on #valid?
end
  

Когда я определяю ассоциации на каждой фабрике:

 Factory.define :user do |f|
  f.association :account
end

Factory.define :account do |f|
  f.association :user
end
  

Я получаю переполнение стека, поскольку каждая из них рекурсивно создает учетную запись / пользователя.

Способ, которым я смог решить эту проблему, заключается в эмуляции вложенных форм атрибутов в моих тестах:

 before :each do
  account_attributes = Factory.attributes_for :account
  account_attributes[:user_attributes] = Factory.attributes_for :user
  @account = Account.new(account_attributes)
end
  

Однако я хотел бы сохранить эту логику на фабрике, поскольку она может выйти из-под контроля, как только я начну добавлять другие модули:

 before :each do
  account_attributes = Factory.attributes_for :account
  account_attributes[:user_attributes] = Factory.attributes_for :user
  account_attributes[:user_attributes][:profile_attributes] = Factory.attributes_for :profile
  account_attributes[:payment_profile_attributes] = Factory.attributes_for :payment_profile
  account_attributes[:subscription_attributes] = Factory.attributes_for :subscription
  @account = Account.new(account_attributes)
end
  

Пожалуйста, помогите!

Ответ №1:

Я смог решить эту проблему, используя обратный вызов factory_girl after_build.

 Factory.define :account do |f|
  f.after_build do |account|
    account.user ||= Factory.build(:user, :account => account)
    account.payment_profile ||= Factory.build(:payment_profile, :account => account)
    account.subscription ||= Factory.build(:subscription, :account => account)
  end
end

Factory.define :user do |f|
  f.after_build do |user|
    user.account ||= Factory.build(:account, :user => user)
    user.profile ||= Factory.build(:profile, :user => user)
  end
end
  

Это создаст связанные классы до того, как класс-владелец будет сохранен, поэтому проверки пройдут.

Комментарии:

1. откуда user.profile взялся? Это подразумевается user.account ?

2. user.profile — это модель, которая хранит информацию о пользователе, связанном с учетной записью. Итак, структура — Учетная запись> Пользователь> Профиль. Однако я добавил user.account, поскольку считаю, что это необходимо при непосредственном создании пользователя.

Ответ №2:

Взгляните на документацию factory_girl. Способ, которым вы создаете эти учетные записи, кажется, что вы на самом деле не используете преимущества factory_girl.

Я всегда заботился об ассоциациях, создавая нужные мне объекты перед тестированием. Я собираюсь попробовать это на основе моделей, на которые вы ссылаетесь выше:

 before :each do
  @account = Factory(:account, :user_id => Factory(:user).id, :profile_id => Factory(:profile).id)
end
  

Теперь @account будут иметь @account.user и @account.profile доступны. Если вам нужно их определить, @profile = @account.profile работает просто отлично.

Комментарии:

1. Проблема, с которой я сталкиваюсь, заключается в том, что user belongs_to account а не наоборот, поэтому учетная запись #user_id не существует, а User #account_id — это атрибут, который необходимо установить. Однако я не могу сохранить учетную запись для получения идентификатора без привязки учетной записи к соответствующему пользователю.