diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c7d38b..ef0d350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # Changelog ## Unreleased - xxxx-xx-xx -- `db_belongs_to` handles a violated composite foreign key on PostgreSQL. Its error names every column of the key, `Key (tenant_id, company_id)`; each is now a candidate, so the association's `must exist` error is added instead of the exception being raised + +## [2.1.2] - 2026-10-27 +- Fix `db_belongs_to` raising `ActiveRecord::InvalidForeignKey` for a violated composite foreign key on PostgreSQL. Its error names every column of the key, `Key (tenant_id, company_id)`; each is now a candidate, so the association's `must exist` error is added instead - Fix `validates_db_uniqueness_of` raising `NoMethodError` on PostgreSQL when the table has row-level security. PostgreSQL omits the `Key (...)=(...)` detail from the unique violation there, so the error is now matched by its index name only ## [2.1.1] - 2026-03-31 diff --git a/database_validations.gemspec b/database_validations.gemspec index fdd4a4a..1fe9a4f 100644 --- a/database_validations.gemspec +++ b/database_validations.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |spec| spec.email = ['lawliet.djez@gmail.com'] spec.summary = 'Provide compatibility between database constraints and ActiveRecord validations with better performance and consistency.' - spec.description = "ActiveRecord provides validations on app level but it won't guarantee the + spec.description = "ActiveRecord provides validations on app level but it won't guarantee the consistent. In some cases, like `validates_uniqueness_of` it executes additional SQL query to the database and that is not very efficient. diff --git a/lib/database_validations/version.rb b/lib/database_validations/version.rb index ec67b64..8da962d 100644 --- a/lib/database_validations/version.rb +++ b/lib/database_validations/version.rb @@ -1,3 +1,3 @@ module DatabaseValidations - VERSION = '2.1.1'.freeze + VERSION = '2.1.2'.freeze end diff --git a/spec/validations/db_belongs_to_spec.rb b/spec/validations/db_belongs_to_spec.rb index 2cbdd53..a320a69 100644 --- a/spec/validations/db_belongs_to_spec.rb +++ b/spec/validations/db_belongs_to_spec.rb @@ -142,7 +142,7 @@ def define_tables end end - describe 'multiple db_belongs_to associations' do # rubocop:disable RSpec/MultipleMemoizedHelpers: + describe 'multiple db_belongs_to associations' do # rubocop:disable RSpec/MultipleMemoizedHelpers let(:department_klass) { define_class(Department, :departments) } let(:multi_fk_klass) do diff --git a/spec/validations/validates_db_uniqueness_of_spec.rb b/spec/validations/validates_db_uniqueness_of_spec.rb index 72321b2..5713881 100644 --- a/spec/validations/validates_db_uniqueness_of_spec.rb +++ b/spec/validations/validates_db_uniqueness_of_spec.rb @@ -8,6 +8,13 @@ before { parent_class.create!(persisted_attrs) unless skip_persisted } + # Rails main (8.2.0.alpha) adds the conflicting record's id to uniqueness error details as `existing_id`. + # db_uniqueness's error on save comes from the unique violation, which carries no id, so the id is taken out + # of app_uniqueness's details before comparing. + def details_without_existing_id(record) + record.errors.details.to_h.transform_values { |details| details.map { |detail| detail.except(:existing_id) } } + end + describe 'valid?' do it 'makes a query for validation' do expect { db_uniqueness.new(persisted_attrs).valid? } @@ -74,7 +81,7 @@ expect(new.errors.details).to be_present expect(old.errors.messages.to_h).to include(new.errors.messages.to_h) - expect(old.errors.details.to_h).to include(new.errors.details.to_h) + expect(details_without_existing_id(old)).to include(new.errors.details.to_h) end context 'when wrapped by transaction' do @@ -91,7 +98,7 @@ expect(new.errors.details).to be_present expect(old.errors.messages.to_h).to include(new.errors.messages.to_h) - expect(old.errors.details.to_h).to include(new.errors.details.to_h) + expect(details_without_existing_id(old)).to include(new.errors.details.to_h) end end end