Ruby on rails 在具有唯一约束的关联的Rails中使用factory\u。获取重复错误
我正在使用Rails2.2项目来更新它。我正在用工厂(使用factory_girl)替换现有的固定装置,并且出现了一些问题。问题在于用查找数据表示表的模型。当我用两个具有相同产品类型的产品创建购物车时,每个创建的产品都在重新创建相同的产品类型。此错误源于ProductType模型上的唯一验证 问题演示 这是一个单元测试,我创建了一个购物车,并将其组装成若干块。我不得不这样做来回避这个问题。不过,这仍然说明了问题所在。我会解释的Ruby on rails 在具有唯一约束的关联的Rails中使用factory\u。获取重复错误,ruby-on-rails,ruby,unit-testing,fixtures,factory-bot,Ruby On Rails,Ruby,Unit Testing,Fixtures,Factory Bot,我正在使用Rails2.2项目来更新它。我正在用工厂(使用factory_girl)替换现有的固定装置,并且出现了一些问题。问题在于用查找数据表示表的模型。当我用两个具有相同产品类型的产品创建购物车时,每个创建的产品都在重新创建相同的产品类型。此错误源于ProductType模型上的唯一验证 问题演示 这是一个单元测试,我创建了一个购物车,并将其组装成若干块。我不得不这样做来回避这个问题。不过,这仍然说明了问题所在。我会解释的 cart = Factory(:cart) cart.cart_it
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
问题:
使用“挑选名单”类型关联的工厂女孩的最佳方式是什么
我希望工厂定义包含所有内容,而不是在测试中组装它,尽管我可以接受它
背景和额外细节
工厂/产品.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
当我试图用两个相同产品类型的项目定义购物车时,我得到了上面描述的相同错误
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
我也遇到过同样的问题,我想也是这里提到的问题:
我认为你的变通办法可能是解决这个问题的最好办法。我想我至少找到了一个更干净的办法 我喜欢联系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
这意味着修改后的工厂代码可以如下所示
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\u girl的序列作为产品类型名称和代码字段?对于大多数测试,我想您不会关心产品类型的代码是“Code1”还是“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
当单身人士被引入工厂时,这些问题将被消除——目前的情况是-
问题-我遇到了同样的问题,并在我的工厂文件顶部添加了一个lambda,该文件实现了单例模式,如果自上一轮测试/规范以来数据库已被清除,则该模式也会重新生成模型:
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
然后,使用示例工厂,可以使用工厂属性来运行lambda
Factory.define :product do |p|
p.product_type { single_instances[:add_product_type] }
#...this block edited as per comment below
end
瞧 编辑:在这个答案的底部可以看到一个更干净的解决方案 原始答案:
这是我创建FactoryGirl singleton关联的解决方案:
FactoryGirl.define do
factory :platform do
name 'Foo'
end
factory :platform_version do
name 'Bar'
platform {
if Platform.find(:first).blank?
FactoryGirl.create(:platform)
else
Platform.find(:first)
end
}
end
end
你可以这样称呼它:
And the following platform versions exists:
| Name |
| Master |
| Slave |
| Replica |
通过这种方式,所有3个平台版本都将具有相同的平台“Foo”,即singleton
如果要保存数据库查询,可以执行以下操作:
platform {
search = Platform.find(:first)
if search.blank?
FactoryGirl.create(:platform)
else
search
end
}
你可以考虑使单身联想有一个特点:
factory :platform_version do
name 'Bar'
platform
trait :singleton do
platform {
search = Platform.find(:first)
if search.blank?
FactoryGirl.create(:platform)
else
search
end
}
end
factory :singleton_platform_version, :traits => [:singleton]
end
如果您想要设置多个平台,并且具有不同的平台_版本集,则可以创建更具体的不同特征,即:
factory :platform_version do
name 'Bar'
platform
trait :singleton do
platform {
search = Platform.find(:first)
if search.blank?
FactoryGirl.create(:platform)
else
search
end
}
end
trait :newfoo do
platform {
search = Platform.find_by_name('NewFoo')
if search.blank?
FactoryGirl.create(:platform, :name => 'NewFoo')
else
search
end
}
end
factory :singleton_platform_version, :traits => [:singleton]
factory :newfoo_platform_version, :traits => [:newfoo]
end
希望这对其他人有用
编辑:在上面提交了我的原始解决方案之后,我对代码进行了另一次检查,并找到了一种更简洁的方法来实现这一点:您不在工厂中定义特性,而是在调用测试步骤时指定关联 建立正规工厂:
FactoryGirl.define do
factory :platform do
name 'Foo'
end
factory :platform_version do
name 'Bar'
platform
end
end
现在,使用指定的关联调用测试步骤:
And the following platform versions exists:
| Name | Platform |
| Master | Name: NewFoo |
| Slave | Name: NewFoo |
| Replica | Name: NewFoo |
这样做时,平台“NewFoo”的创建使用“find_或_create_by”功能,因此第一个调用创建平台,接下来的两个调用查找已创建的平台
这样,所有3个平台版本都将具有相同的平台“NewFoo”,并且您可以根据需要创建任意多个平台版本集
我认为这是一个非常干净的解决方案,因为你保持了工厂的清洁,你甚至让你的测试步骤的读者看到这3个平台版本都有相同的平台。我也有类似的情况。我最终使用seeds.rb来定义单例,然后要求spec_helper.rb中的seeds.rb将对象创建到测试数据库中。然后我就可以在工厂里搜索合适的对象了 db/seeds.rb
RegionType.find_or_create_by_region_type('community')
RegionType.find_or_create_by_region_type('province')
spec/spec_helper.rb
require "#{Rails.root}/db/seeds.rb"
spec/factory.rb
FactoryGirl.define do
factory :region_community, class: Region do
sequence(:name) { |n| "Community#{n}" }
region_type { RegionType.find_by_region_type("community") }
end
end
仅供参考,您也可以在工厂内使用宏,检查对象是否已存在,然后不要再次创建它。使用lambda的解决方案(很棒,但是!)是复制find_或create_by中已经存在的逻辑。这也适用于通过以下方式创建:联盟的协会:
FactoryGirl.define do
factory :region_community, class: Region do
sequence(:name) { |n| "Community#{n}" }
region_type { RegionType.find_by_region_type("community") }
end
end
FactoryGirl.define do
factory :league, :aliases => [:euro_cup] do
id 1
name "European Championship"
rank 30
initialize_with { League.find_or_create_by_id(id)}
end
end
FactoryGirl.define do
factory :platform do
name 'Foo'
end
factory :platform_version do
name 'Bar'
platform { Platform.first || create(:platform) }
end
end
FactoryGirl.create :platform_version
FactoryGirl.create :platform_version, name: 'Car'
FactoryGirl.create :platform_version, name: 'Dar'
=>
-------------------
platform_versions
-------------------
name | platform
------+------------
Bar | Foo
Car | Foo
Dar | Foo
FactoryGirl.create :platform_version
FactoryGirl.create :platform_version, name: 'Car'
FactoryGirl.create :platform_version, name: 'Dar', platform: create(:platform, name: 'Goo')
=>
-------------------
platform_versions
-------------------
name | platform
------+------------
Bar | Foo
Car | Foo
Dar | Goo