Используя factory_girl в направляющих с ассоциациями, которые имеют ограничения на уникальность данных. Получение дублирующихся ошибок

Я работаю с направляющие 2,2 проекта, работающие для обновления его. Я заменяю существующие приспособления фабриками (использующий factory_girl) и имел некоторые проблемы. Проблема с моделями, которые представляют таблицы с данными поиска. Когда я создаю Корзину с двумя продуктами, которые имеют тот же тип продукта, каждый созданный продукт воссоздает тот же тип продукта. Это ошибки от уникальной проверки на модели ProductType.

Проблемная демонстрация

Это от модульного теста, где я создаю Корзину и соединяю ее в частях. Я должен был сделать это для обхождения проблемы. Это все еще демонстрирует проблему все же. Я объясню.

cart = Factory(:cart)
cart.cart_items = [Factory(:cart_item, 
                           :cart => cart, 
                           :product => Factory(:added_users_product)),
                   Factory(:cart_item, 
                           :cart => cart, 
                           :product => Factory(:added_profiles_product))]

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

Ошибка, которая сгенерирована: "ActiveRecord:: RecordInvalid: Проверка перестала работать: Имя было уже взято, Код был уже взят"

Обходное решение

Обходное решение для этого примера должно переопределить используемый тип продукта и передача в определенном экземпляре, таким образом, только один экземпляр используется. "add_product_type" выбирается рано и передается в для каждого объекта корзины.

cart = Factory(:cart)
prod_type = Factory(:add_product_type)   #New
cart.cart_items = [Factory(:cart_item,
                           :cart => cart,
                           :product => Factory(:added_users_product,
                                               :product_type => prod_type)), #New
                   Factory(:cart_item,
                           :cart => cart,
                           :product => Factory(:added_profiles_product,
                                               :product_type => prod_type))] #New

Вопрос

Что лучший способ состоит в том, чтобы использовать factory_girl с типами "списка выбора" ассоциаций?

Я хотел бы за определение фабрики содержать все вместо того, чтобы иметь необходимость собрать его в тесте, хотя я могу жить с ним.

Фон и дополнительные детали

factories/product.rb

# Declare ProductTypes

Factory.define :product_type do |t|
  t.name "None"
  t.code "none"
end

Factory.define :sub_product_type, :parent => :product_type do |t|
  t.name "Subscription"
  t.code "sub"
end

Factory.define :add_product_type, :parent => :product_type do |t|
  t.name "Additions"
  t.code "add"
end

# Declare Products

Factory.define :product do |p|
  p.association :product_type, :factory => :add_product_type
  #...
end

Factory.define :added_profiles_product, :parent => :product do |p|
  p.association :product_type, :factory => :add_product_type
  #...
end

Factory.define :added_users_product, :parent => :product do |p|
  p.association :product_type, :factory => :add_product_type
  #...
end

Целью "кода" ProductType является так приложение, может дать особое значение им. Модель ProductType выглядит примерно так:

class ProductType < ActiveRecord::Base
  has_many :products

  validates_presence_of :name, :code
  validates_uniqueness_of :name, :code
  #...
end

factories/cart.rb

# Define Cart Items

Factory.define :cart_item do |i|
  i.association :cart
  i.association :product, :factory => :test_product
  i.quantity 1
end

Factory.define :cart_item_sub, :parent => :cart_item do |i|
  i.association :product, :factory => :year_sub_product
end

Factory.define :cart_item_add_profiles, :parent => :cart_item do |i|
  i.association :product, :factory => :add_profiles_product
end

# Define Carts

# Define a basic cart class. No cart_items as it creates dups with lookup types.
Factory.define :cart do |c|
  c.association :account, :factory => :trial_account
end

Factory.define :cart_with_two_different_items, :parent => :cart do |o|
  o.after_build do |cart|
    cart.cart_items = [Factory(:cart_item, 
                               :cart => cart, 
                               :product => Factory(:year_sub_product)),
                       Factory(:cart_item, 
                               :cart => cart, 
                               :product => Factory(:added_profiles_product))]
  end
end

Когда я пытаюсь определить корзину с двумя объектами того же типа продукта, я описал ту же ошибку выше.

Factory.define :cart_with_two_add_items, :parent => :cart do |o|
  o.after_build do |cart|
    cart.cart_items = [Factory(:cart_item,
                               :cart => cart,
                               :product => Factory(:added_users_product)),
                       Factory(:cart_item,
                               :cart => cart,
                               :product => Factory(:added_profiles_product))]
  end
end
40
задан Mark Eric 6 January 2010 в 19:10
поделиться

6 ответов

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

saved_single_instances = {}
#Find or create the model instance
single_instances = lambda do |factory_key|
  begin
    saved_single_instances[factory_key].reload
  rescue NoMethodError, ActiveRecord::RecordNotFound  
    #was never created (is nil) or was cleared from db
    saved_single_instances[factory_key] = Factory.create(factory_key)  #recreate
  end

  return saved_single_instances[factory_key]
end

Затем, используя ваши примеры фабрик, вы можете использовать ленивый атрибут factory_girl для запуска лямбда

Factory.define :product do |p|
  p.product_type  { single_instances[:add_product_type] }
  #...this block edited as per comment below
end

Вуаля!

31
ответ дан 27 November 2019 в 01:28
поделиться

У меня была та же проблема, и я думаю, что это та же самая, на которую здесь ссылались: http://groups.google.com/group/factory_girl/browse_frm/thread/68947290d1819952/ef22581f4cd05aa9?tvc=1&q=associations+validates_uniqueness_of#ef22581f4cd05aa9

Я думаю, что ваш обходной путь, возможно, лучшее решение проблемы.

1
ответ дан 27 November 2019 в 01:28
поделиться

Думаю, я хотя бы нашел более чистый способ.

Мне нравится идея связаться с ThoughtBot, чтобы получить рекомендуемое "официальное" решение. Пока это хорошо работает.

Я только что объединил подход, делая это в коде теста и делая все это в заводском определении.

Factory.define :cart_with_two_add_items, :parent => :cart do |o|
  o.after_build do |cart|
    prod_type = Factory(:add_product_type) # Define locally here and reuse below

    cart.cart_items = [Factory(:cart_item,
                               :cart => cart,
                               :product => Factory(:added_users_product,
                                                   :product_type => prod_type)),
                       Factory(:cart_item,
                               :cart => cart,
                               :product => Factory(:added_profiles_product,
                                                   :product_type => prod_type))]
  end
end

def test_cart_with_same_item_types
  cart = Factory(:cart_with_two_add_items)
  # ... Do asserts
end

Я обновлю, если найду лучшее решение.

0
ответ дан 27 November 2019 в 01:28
поделиться

Короткий ответ: «Нет», заводская девушка не имеет более чистого способа сделать это. Я, казалось, проверил это на форумах фабрики.

Однако я нашел еще один ответ для себя. Это включает в себя еще один вид обходной путь, но делает все намного очиститель.

Идея состоит в том, чтобы изменить модели, которые представляют таблицы поиска для создания необходимой записи, если отсутствуют. Это нормально, потому что код ожидает существующих конкретных записей. Вот пример модифицированной модели.

class ProductType < ActiveRecord::Base
  has_many :products

  validates_presence_of :name, :code
  validates_uniqueness_of :name, :code

  # Constants defined for the class.
  CODE_FOR_SUBSCRIPTION = "sub"
  CODE_FOR_ADDITION = "add"

  # Get the ID for of the entry that represents a trial account status.
  def self.id_for_subscription
    type = ProductType.find(:first, :conditions => ["code = ?", CODE_FOR_SUBSCRIPTION])
    # if the type wasn't found, create it.
    if type.nil?
      type = ProductType.create!(:name => 'Subscription', :code => CODE_FOR_SUBSCRIPTION)
    end
    # Return the loaded or created ID
    type.id
  end

  # Get the ID for of the entry that represents a trial account status.
  def self.id_for_addition
    type = ProductType.find(:first, :conditions => ["code = ?", CODE_FOR_ADDITION])
    # if the type wasn't found, create it.
    if type.nil?
      type = ProductType.create!(:name => 'Additions', :code => CODE_FOR_ADDITION)
    end
    # Return the loaded or created ID
    type.id
  end
end

Способ статического класса «ID_FOR_ADDITIOR» будет загружать модель и идентификатор, если она найден, если она не найдена, это создаст ее.

Недостаток - это метод «ID_FOR_DDITIOR», может быть не понятен относительно того, что он делает по его названию. Это может потребоваться изменить. Единственное другое влияние кода для нормального использования - это дополнительный тест, чтобы увидеть, была ли модель была найдена или нет.

Это означает, что заводский код для создания продукта можно изменить, как это ...

Factory.define :added_users_product, :parent => :product do |p|
  #p.association :product_type, :factory => :add_product_type
  p.product_type_id { ProductType.id_for_addition }
end

Это означает, что модифицированный фабричный код может выглядеть так ...

Factory.define :cart_with_two_add_items, :parent => :cart do |o|
  o.after_build do |cart|
    cart.cart_items = [Factory(:cart_item_add_users, :cart => cart),
                       Factory(:cart_item_add_profiles, :cart => cart)]
  end
end

Это именно то, что я хотел. Теперь я могу чисто выразить свой фабричный код и тестовый код.

Еще одно преимущество этого подхода - данные таблицы поиска не должны посещать или заполняться миграциями. Он будет справиться с тестовыми базами данных, а также производства.

2
ответ дан 27 November 2019 в 01:28
поделиться
-

Может быть, вы можете попробовать использовать последовательности Factory_Girl для имени типа и кодовых полей? Для большинства тестов, которые я думаю, вам не позаботится, является ли код типа продукта «код 1» или «sub», а для тех, где вы заботитесь, вы всегда можете указать это явно.

Factory.sequence(:product_type_name) { |n| "ProductType#{n}" }
Factory.sequence(:product_type_code) { |n| "prod_#{n}" }        

Factory.define :product_type do |t|
  t.name { Factory.next(:product_type_name) }
  t.code { Factory.next(:product_type_code) }
end 
0
ответ дан 27 November 2019 в 01:28
поделиться

Эти проблемы были бы устранены, когда синглтоны вводятся на фабрики - это в настоящее время - http://github.com/roderickvd/factory_girl/tree/singletons Выпуск - http://github.com/thoughtbot/factory_girl/issues#sue/16

2
ответ дан 27 November 2019 в 01:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: