我有一个带有这些验证的模型:
class Tool < ActiveRecord::Base
has_many :repairs
has_many :services
belongs_to :category
belongs_to :location
accepts_nested_attributes_for :repairs
accepts_nested_attributes_for :services
validates :name, :serial, :model, :presence => true
validates_uniqueness_of :serial
end
我的工厂看起来像这样:
require 'faker'
FactoryGirl.define do
factory :tool do
association :location
association :category
name {Faker::Name.first_name}
model {Faker::Company.name}
serial {Faker::Address.latitude}
end
end
我有一些简单的测试:
it 'is valid with a name, serial, model, location, category' do
expect(build(:tool, location: Location.new(name: 'Sta 72'),
category: Category.new(name: 'Chainsaw'))).to be_valid
end
it 'is invalid without a name' do
expect(build(:tool, name: nil)).to have(1).errors_on(:name)
end
it 'is invalid without a serial' do
expect(build(:tool, serial: nil)).to have(1).errors_on(:serial)
end
it 'is invalid without a model' do
expect(build(:tool, model: nil)).to have(1).errors_on(:model)
end
it 'is invalid with a duplicate serial' do
tool1 = create(:tool, serial: '12345')
tool2 = build(:tool, serial: tool1.serial)
expect(tool2).to have(1).errors_on(:serial)
end
除了最后一次检查以确保:serial
是唯一的测试外,一切都很完美。它失败了:
ActiveRecord::RecordInvalid: Validation failed: Name has already been taken
./spec/models/tool_spec.rb:27:in `block (3 levels) in <top (required)>'
当我没有验证Name
的唯一性时,我不知道为什么它抱怨Name
。第27行引用tool2 = build(:tool, serial: tool1.serial)
修改*
schema.rb
看起来像这样:
...
create_table "tools", force: true do |t|
t.string "name"
t.string "serial"
t.date "purchased"
t.date "put_in_service"
t.decimal "cost"
t.decimal "value"
t.boolean "in_service"
t.date "retired"
t.datetime "created_at"
t.datetime "updated_at"
t.text "note"
t.integer "condition"
t.text "old_location"
t.string "model"
t.boolean "loaner", default: false
t.integer "location_id"
t.integer "category_id"
end
add_index "tools", ["category_id"], name: "index_tools_on_category_id", using: :btree
add_index "tools", ["location_id"], name: "index_tools_on_location_id", using: :btree
当我只运行失败的测试时,我的测试日志看起来像这样:
ActiveRecord::SchemaMigration Load (0.6ms) SELECT "schema_migrations".* FROM "schema_migrations"
(0.4ms) BEGIN
(0.3ms) SAVEPOINT active_record_1
SQL (9.3ms) INSERT INTO "locations" ("created_at", "name", "type", "updated_at", "vehicle") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["name", "Loaners"], ["type", "Station"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["vehicle", true]]
(0.3ms) RELEASE SAVEPOINT active_record_1
(0.3ms) SAVEPOINT active_record_1
Category Exists (1.5ms) SELECT 1 AS one FROM "categories" WHERE "categories"."name" = 'Chainsaws' LIMIT 1
SQL (1.2ms) INSERT INTO "categories" ("created_at", "name", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["name", "Chainsaws"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00]]
(0.2ms) RELEASE SAVEPOINT active_record_1
(0.4ms) SAVEPOINT active_record_1
Tool Exists (1.0ms) SELECT 1 AS one FROM "tools" WHERE "tools"."serial" = '12345' LIMIT 1
SQL (1.4ms) INSERT INTO "tools" ("category_id", "created_at", "location_id", "model", "name", "serial", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["category_id", 77], ["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["location_id", 43], ["model", "Stark LLC"], ["name", "Cristina"], ["serial", "12345"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00]]
(0.2ms) RELEASE SAVEPOINT active_record_1
(0.3ms) SAVEPOINT active_record_1
SQL (0.9ms) INSERT INTO "locations" ("created_at", "name", "type", "updated_at", "vehicle") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["name", "Loaners"], ["type", "Station"], ["updated_at", Mon, 27 Jan 2014 22:28:52 PST -08:00], ["vehicle", true]]
(0.3ms) RELEASE SAVEPOINT active_record_1
(0.3ms) SAVEPOINT active_record_1
Category Exists (0.5ms) SELECT 1 AS one FROM "categories" WHERE "categories"."name" = 'Chainsaws' LIMIT 1
(0.3ms) ROLLBACK TO SAVEPOINT active_record_1
(0.6ms) ROLLBACK
编辑 - &GT;
感谢下面的答案,我的问题得到了解决。我根据他们的答案使用的代码是:
it 'is invalid with a duplicate serial' do
chainsaw = create(:category, name: 'Chainsaw')
tool1 = create(:tool, serial: '12345', category: chainsaw)
tool2 = build(:tool, serial: '12345', category: chainsaw)
expect(tool2).to have(1).errors_on(:serial)
end
我不明白为什么FactoryGirl和Faker不会创建这两个工具并尊重validates uniqueness
模型上的Category
约束。它不应该只重用它刚为第一个工具创建的Category
(而不是尝试使用相同的名称创建一个新的)或为不同的新工具创建一个新的Category
比前一个?
AHA!这就是原因:
FactoryGirl.define do
factory :category do
name 'Chainsaws'
end
end
我将这个工厂设置为使用Faker,它应该像我期望的那样工作。
答案 0 :(得分:2)
您似乎在隐式创建Category
实例而不是Tools
实例时收到验证错误。
由于它发生在build(:tool, ...)
调用上,我们知道隐式创建的Category
实例支持与先前创建的build
实例的Category
冲突。在您分享的日志中,我们可以看到前面的create(:tool, ...)
调用和build(:tool, ...)
都在创建名为Category
的{{1}}个实例。
(注意:此答案的早期版本错误地假设与第一个示例存在冲突,但由于第一个示例中的名称为Chainsaws
而不是Chainsaw
,因此没有意义。 ,更不用说最有可能启用交易的可能性,保护最后一个例子免受第一个例子的影响。)
答案 1 :(得分:1)
可能在类别#名称上被触发的唯一性约束。您是否在工厂中将其设置为静态值?
# This causes the associated category to be created
tool1 = create(:tool, serial: '12345')
# The build strategy actually creates associated records.
# This is trying to create a category with a duplicate name.
tool2 = build(:tool, serial: tool1.serial)
如果您更新类别工厂以使用序列或假数据作为名称,这应该有用。