Hi!
I have one model with two column (applicant_id and career_id) and both are foreign keys. When I a try to validate applicant_id or career_id is uniqueness
should validate_uniqueness_of(:applicant_id)
or
should validate_uniqueness_of(:career_id)
I got follow exception
Failure/Error: should validate_uniqueness_of(:applicant_id)
ActiveRecord::InvalidForeignKey:
PG::ForeignKeyViolation: ERROR: insert or update on table "degrees" violates foreign key constraint "fk_rails_25163bce0a"
DETAIL: Key (applicant_id)=(0) is not present in table "applicants".
: INSERT INTO "degrees" ("applicant_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"
But if I delete the foreing key constraint this problem does not occur
The foreign key constraint says that in order for a Degree to refer to an Applicant, that Applicant must already exist with the same id. The constraint is failing when validate_uniqueness_of attempts to save your Degree record (it needs an existing record to compare a new one against). Try creating an Applicant before using validate_uniqueness_of.
I'm closing this as I don't think this is a bug with shoulda-matchers, but feel free to continue the discussion.
It is a bug in shoulda-matchers, since it assign an id not in the database. If not, what causes this error?
FactoryGirl.define do
factory :document_publishing_stage do
closed false
association :document
association :publishing_stage
factory :closed_document_status do
closed true
end
end
end
RSpec.describe DocumentPublishingStage, type: :model do
describe 'with validation' do
subject { build(:document_publishing_stage) }
it { should validate_presence_of(:document) }
it { should validate_presence_of(:publishing_stage) }
it { should validate_uniqueness_of(:document_id).scoped_to(:publishing_stage_id, :ref_id) }
end
end
@itsmechlark What's the error message you're getting?
Hopefully this stack-trace will help you. Thanks.
Failure/Error: it { should validate_uniqueness_of(:document_id).scoped_to(:publishing_stage_id, :ref_id) }
ActiveRecord::InvalidForeignKey:
PG::ForeignKeyViolation: ERROR: insert or update on table "document_publishing_stages" violates foreign key constraint "fk_rails_b88661929f"
DETAIL: Key (document_id)=(0) is not present in table "documents".
: INSERT INTO "document_publishing_stages" ("document_id", "publishing_stage_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" /*application:CDAsiaOnline,line:/spec/models/document_publishing_stage_spec.rb:21:in `block (3 levels) in <top (required)>'*/
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/postgresql_adapter.rb:606:in `exec_prepared'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/postgresql_adapter.rb:606:in `block in exec_cache'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract_adapter.rb:473:in `block in log'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/notifications/instrumenter.rb:20:in `instrument'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract_adapter.rb:467:in `log'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/postgresql_adapter.rb:605:in `exec_cache'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/postgresql_adapter.rb:589:in `execute_and_clear'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:160:in `exec_query'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/marginalia-1.3.0/lib/marginalia.rb:56:in `exec_query_with_marginalia'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/postgresql/database_statements.rb:192:in `exec_insert'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/database_statements.rb:108:in `insert'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/query_cache.rb:14:in `insert'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/relation.rb:64:in `insert'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/persistence.rb:524:in `_create_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/counter_cache.rb:139:in `_create_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/locking/optimistic.rb:75:in `_create_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/attribute_methods/dirty.rb:132:in `_create_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/callbacks.rb:306:in `block in _create_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:117:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:117:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:555:in `block (2 levels) in compile'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:505:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:505:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:92:in `__run_callbacks__'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:778:in `_run_create_callbacks'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/callbacks.rb:306:in `_create_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/timestamp.rb:57:in `_create_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/persistence.rb:504:in `create_or_update'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/callbacks.rb:302:in `block in create_or_update'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:117:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:117:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:555:in `block (2 levels) in compile'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:505:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:505:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:92:in `__run_callbacks__'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.4/lib/active_support/callbacks.rb:778:in `_run_save_callbacks'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/callbacks.rb:302:in `create_or_update'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/persistence.rb:120:in `save'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/validations.rb:37:in `save'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/attribute_methods/dirty.rb:21:in `save'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/transactions.rb:286:in `block (2 levels) in save'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/transactions.rb:351:in `block in with_transaction_returning_status'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `block in transaction'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/transaction.rb:184:in `within_new_transaction'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/connection_adapters/abstract/database_statements.rb:213:in `transaction'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/transactions.rb:220:in `transaction'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/transactions.rb:348:in `with_transaction_returning_status'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/transactions.rb:286:in `block in save'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/transactions.rb:301:in `rollback_active_record_state!'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/activerecord-4.2.4/lib/active_record/transactions.rb:285:in `save'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:356:in `block in create_record_in_database'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:353:in `tap'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:353:in `create_record_in_database'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:329:in `first_instance'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:325:in `existing_record'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:385:in `block in set_scoped_attributes'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:382:in `each'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:382:in `all?'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:382:in `set_scoped_attributes'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/shoulda-matchers-3.0.0/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb:263:in `matches?'
# ./spec/models/document_publishing_stage_spec.rb:21:in `block (3 levels) in <top (required)>'
# ./spec/config/database_cleaner.rb:11:in `block (3 levels) in <top (required)>'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/database_cleaner-1.5.1/lib/database_cleaner/generic/base.rb:16:in `cleaning'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/database_cleaner-1.5.1/lib/database_cleaner/base.rb:92:in `cleaning'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/database_cleaner-1.5.1/lib/database_cleaner/configuration.rb:86:in `block (2 levels) in cleaning'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/database_cleaner-1.5.1/lib/database_cleaner/configuration.rb:87:in `call'
# /home/itsmechlark/.rvm/gems/ruby-2.2.3/gems/database_cleaner-1.5.1/lib/database_cleaner/configuration.rb:87:in `cleaning'
# ./spec/config/database_cleaner.rb:10:in `block (2 levels) in <top (required)>'
@itsmechlark So here's what the matcher is doing (or supposed to do anyway):
document_id pointing to a real Documentdocument_id to some default value, then saves itdocument_id to the same value, and asserts that it fails the uniqueness validationThe problem is that it's failing on step 2, because the default value it sets document_id to ends up being 0. Since this doesn't point to a real Document, when that first instance is saved, the foreign key constraint you have on the table fails.
So now that I've had a second look at this, I do agree that this can probably be fixed from the shoulda-matchers side. Specifically, I'm curious what would happen if we didn't override the attribute being tested with a new, default value, as we are doing. So I'll reopen this issue to remind myself to investigate that.
In the meantime, what would happen if you used an existing instance of DocumentPublishingStage instead of a new (unsaved) instance? In other words:
it do
create(:document_publishing_stage)
should validate_uniqueness_of(:document_id).scoped_to(:publishing_stage_id, :ref_id)
end
I tried:
subject { create(:document_publishing_stage) }
it { should validate_uniqueness_of(:document_id).scoped_to(:publishing_stage_id, :ref_id) }
it do
create(:document_publishing_stage) }
should validate_uniqueness_of(:document_id).scoped_to(:publishing_stage_id, :ref_id)
end
still not working. I was thinking during validation:
subject { create(:document_publishing_stage) }
it { should validate_uniqueness_of(:document_id).scoped_to(:publishing_stage_id, :ref_id) }
:document_id plus the scope :publishing_stage_id and ref_id from the subjectYeah, the values of scopes are copied from the subject, but for some reason the value of the attribute (document_id) isn't.
So you're saying that if you create the record instead of building it you get the exact same error?
No, it didn't copy values of scope. I found out this when one of my custom attribute was assign with nil value, ref_id. We should add test regarding foreign key constraint.
Okay. I'm not sure when I'll be able to get to this, but in the meantime do you think it's possible for you to write a failing test for this and submit that as a PR?
I am not familiar neither with shoulda-matchers nor with rspec however the issue has gone away when I've rewritten the test in the next way
it do
subject.document = FactoryGirl.build(:document)
it { should validate_uniqueness_of(:document_id).scoped_to(:publishing_stage_id, :ref_id) }
end
hope it helps someone at least as a workaround.
@tamatsyk Ah, interesting. You could also try creating the record in question beforehand; that might be more straightforward than setting subject.document.
I have the same problem, and any solution worked for me ):
I solved it the same way @tarasmatsyk solved it.
it do
subject.user = FactoryGirl.create(:user, organization: subject.organization)
is_expected.to validate_uniqueness_of(:user_id).scoped_to(:organization_id).allow_nil
end
Funnily enough, this didn't happen before, only after refactoring the my specs which contained a subtle error in my factories.
@tarasmatsyk Thank you!
I also ran into this issue, and fixed it with @tarasmatsyk's solution:
it do
# https://github.com/thoughtbot/shoulda-matchers/issues/682
subject.user = create(:user)
is_expected.to validate_uniqueness_of(:user_id).scoped_to(:organization_id)
end
I solved the problem by creating the factory instead of building it for the specific context.
context do
subject { create(: document_publishing_stage) }
it do
is_expected.to validate_uniqueness_of(:user_id).scoped_to(:organization_id)
end
end
Hey folks. In an effort to lighten our load as maintainers and be able to serve you better in the future, the shoulda-matchers team is working on cleaning out the cobwebs in this repo by pruning the backlog. As there are few of us, there are a lot of items that will simply never earn our attention in a reasonable time frame, and rather than giving you an empty promise, we think it makes more sense to focus on more recent issues. That means, unfortunately, that we must close this issue.
Don't take this the wrong way: our aim is not to diminish the effort people have made or dismiss problems that have been raised. If you feel that we should reopen this issue, then please let us know so that we can reprioritize it. Thanks!
To anyone else coming across this issue, I fixed my problem by creating a record with zero for an ID (so when shoulda-matchers assigns the zero as a dummy value, the foreign-keyed model exists in the test database)
Most helpful comment
I am not familiar neither with shoulda-matchers nor with rspec however the issue has gone away when I've rewritten the test in the next way
hope it helps someone at least as a workaround.