Reputation: 6411
I have a model with these validations:
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
My factory look like this:
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
And I have some simple tests like these:
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
Everything works perfect except the last test that checks to make sure :serial
is unique. It fails with this:
ActiveRecord::RecordInvalid: Validation failed: Name has already been taken
./spec/models/tool_spec.rb:27:in `block (3 levels) in <top (required)>'
I don't know why it complains about Name
when I'm not validating uniqueness of Name
. Line 27 refers to tool2 = build(:tool, serial: tool1.serial)
edit*
schema.rb
looks like this:
...
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
my test log looks like this when I run just the test that's failing:
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
EDIT->
Thanks to the answers below my problem was solved. The code I used based on their answer is:
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
What I don't understand is why FactoryGirl and Faker wouldn't create the two tools and honor the validates uniqueness
constraint on the Category
model. Shouldn't it either just reuse the Category
it just created for the first tool (rather than try to create a new on with the same name) or create a new Category
for the new tool that is different than the previous one?
aha! this is why:
FactoryGirl.define do
factory :category do
name 'Chainsaws'
end
end
I'll set this factory to use Faker and it should work as I was expecting.
Upvotes: 1
Views: 1886
Reputation: 29389
It looks like you're getting a validation error on the implicit creation of the Category
instance, not of the Tools
instance.
Since it's occurring on the build(:tool, ...)
call, we know that the Category
instance implicitly created to support the build
conflicts with a previously created Category
instance. From the log you shared, we can see that both the preceding create(:tool, ...)
call and the build(:tool, ...)
are both creating Category
instances with a name of Chainsaws
.
(Note: An earlier version of this answer mistakenly hypothesized a conflict with the first example, but that didn't make sense since the name in the first example was Chainsaw
rather than Chainsaws
, not to mention the likelihood that transactions are most likely enabled, protecting the last example from the impact of the first.)
Upvotes: 3
Reputation: 3507
The uniqueness constraint being tripped is likely on Category#name. Are you setting that to a static value in your factory?
# 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)
If you update your Category factory to use a sequence or fake data for name, this should work.
Upvotes: 1