diff --git a/lib/shoulda/matchers/active_record/association_matcher.rb b/lib/shoulda/matchers/active_record/association_matcher.rb index 0aa57e29..c410e5dc 100644 --- a/lib/shoulda/matchers/active_record/association_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matcher.rb @@ -957,7 +957,8 @@ module Shoulda end def join_table_matcher - @join_table_matcher ||= AssociationMatchers::JoinTableMatcher.new(self) + @join_table_matcher ||= + AssociationMatchers::JoinTableMatcher.new(self, reflector) end def class_exists? diff --git a/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb b/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb index 1ac63640..0c9ade18 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb @@ -4,7 +4,8 @@ module Shoulda module AssociationMatchers # @private class JoinTableMatcher - attr_reader :association_matcher, :failure_message + attr_reader :failure_message + alias :missing_option :failure_message delegate :model_class, :join_table, :associated_class, @@ -12,8 +13,9 @@ module Shoulda delegate :connection, to: :model_class - def initialize(association_matcher) + def initialize(association_matcher, reflector) @association_matcher = association_matcher + @reflector = reflector end def matches?(subject) @@ -39,8 +41,14 @@ module Shoulda end end + protected + + attr_reader :association_matcher, :reflector + private + delegate :foreign_key, :association_foreign_key, to: :reflector + def missing_columns @missing_columns ||= expected_join_table_columns.select do |key| !actual_join_table_columns.include?(key) @@ -48,10 +56,7 @@ module Shoulda end def expected_join_table_columns - [ - "#{model_class.name.underscore}_id", - "#{associated_class.name.underscore}_id" - ] + [foreign_key, association_foreign_key] end def actual_join_table_columns diff --git a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb index fc6b38a0..ec5ea912 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb @@ -45,6 +45,25 @@ module Shoulda end end + def foreign_key + if has_and_belongs_to_many_reflection + has_and_belongs_to_many_reflection.foreign_key + elsif reflection.respond_to?(:primary_key_name) + reflection.primary_key_name + else + reflection.foreign_key + end + end + + def association_foreign_key + if has_and_belongs_to_many_reflection + join_model = has_and_belongs_to_many_reflection.options[:class] + join_model.right_reflection.foreign_key + else + reflection.association_foreign_key + end + end + protected attr_reader :reflection, :subject diff --git a/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb b/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb index c9734bed..1deaf2ff 100644 --- a/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +++ b/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb @@ -5,7 +5,8 @@ module Shoulda # @private class ModelReflector delegate :associated_class, :through?, :join_table, - :association_relation, :polymorphic?, to: :reflection + :association_relation, :polymorphic?, :foreign_key, + :association_foreign_key, to: :reflection def initialize(subject, name) @subject = subject diff --git a/spec/shoulda/matchers/active_record/association_matcher_spec.rb b/spec/shoulda/matchers/active_record/association_matcher_spec.rb index 292adca5..e7a43b56 100644 --- a/spec/shoulda/matchers/active_record/association_matcher_spec.rb +++ b/spec/shoulda/matchers/active_record/association_matcher_spec.rb @@ -732,6 +732,44 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do end.to fail_with_message_including('missing columns: person_id, relative_id') end + context 'using a custom foreign key' do + it 'rejects an association with a join table with incorrect columns' do + define_model :relative + define_model :person do + has_and_belongs_to_many :relatives, + foreign_key: :custom_foreign_key_id + end + + define_model :people_relative, + id: false, + custom_foreign_key_id: :integer, + some_crazy_id: :integer + + expect do + expect(Person.new).to have_and_belong_to_many(:relatives) + end.to fail_with_message_including('missing columns: custom_foreign_key_id, relative_id') + end + end + + context 'using a custom association foreign key' do + it 'rejects an association with a join table with incorrect columns' do + define_model :relative + define_model :person do + has_and_belongs_to_many :relatives, + association_foreign_key: :custom_association_foreign_key_id + end + + define_model :people_relative, + id: false, + custom_association_foreign_key_id: :integer, + some_crazy_id: :integer + + expect do + expect(Person.new).to have_and_belong_to_many(:relatives) + end.to fail_with_message_including('missing columns: person_id, custom_association_foreign_key_id') + end + end + it 'rejects an association of the wrong type' do define_model :relative, person_id: :integer define_model :person do