Я работаю с направляющие 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
. Я столкнулся с той же проблемой и добавил лямбда-выражение в верхней части файла фабрик, который реализует одноэлементный шаблон, который также регенерирует модель, если база данных была очищена с момента последнего раунда тестов / спецификаций:
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
Вуаля!
У меня была та же проблема, и я думаю, что это та же самая, на которую здесь ссылались: http://groups.google.com/group/factory_girl/browse_frm/thread/68947290d1819952/ef22581f4cd05aa9?tvc=1&q=associations+validates_uniqueness_of#ef22581f4cd05aa9
Я думаю, что ваш обходной путь, возможно, лучшее решение проблемы.
Думаю, я хотя бы нашел более чистый способ.
Мне нравится идея связаться с 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
Я обновлю, если найду лучшее решение.
Короткий ответ: «Нет», заводская девушка не имеет более чистого способа сделать это. Я, казалось, проверил это на форумах фабрики.
Однако я нашел еще один ответ для себя. Это включает в себя еще один вид обходной путь, но делает все намного очиститель.
Идея состоит в том, чтобы изменить модели, которые представляют таблицы поиска для создания необходимой записи, если отсутствуют. Это нормально, потому что код ожидает существующих конкретных записей. Вот пример модифицированной модели.
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
Это именно то, что я хотел. Теперь я могу чисто выразить свой фабричный код и тестовый код.
Еще одно преимущество этого подхода - данные таблицы поиска не должны посещать или заполняться миграциями. Он будет справиться с тестовыми базами данных, а также производства.
Может быть, вы можете попробовать использовать последовательности 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
Эти проблемы были бы устранены, когда синглтоны вводятся на фабрики - это в настоящее время - http://github.com/roderickvd/factory_girl/tree/singletons Выпуск - http://github.com/thoughtbot/factory_girl/issues#sue/16