Check correct columns are present in join table
The `have_and_belong_to_many` matcher could give a false positive if the join table is present but does not contain the correct columns. Check to see if the columns exist in the join table and provide a meaningful failure message if one or more of the columns are not present.
This commit is contained in:
parent
f356561f6e
commit
0695c8647b
|
@ -2,6 +2,7 @@ require 'shoulda/matchers/active_record/association_matcher'
|
||||||
require 'shoulda/matchers/active_record/association_matchers'
|
require 'shoulda/matchers/active_record/association_matchers'
|
||||||
require 'shoulda/matchers/active_record/association_matchers/counter_cache_matcher'
|
require 'shoulda/matchers/active_record/association_matchers/counter_cache_matcher'
|
||||||
require 'shoulda/matchers/active_record/association_matchers/inverse_of_matcher'
|
require 'shoulda/matchers/active_record/association_matchers/inverse_of_matcher'
|
||||||
|
require 'shoulda/matchers/active_record/association_matchers/join_table_matcher'
|
||||||
require 'shoulda/matchers/active_record/association_matchers/order_matcher'
|
require 'shoulda/matchers/active_record/association_matchers/order_matcher'
|
||||||
require 'shoulda/matchers/active_record/association_matchers/through_matcher'
|
require 'shoulda/matchers/active_record/association_matchers/through_matcher'
|
||||||
require 'shoulda/matchers/active_record/association_matchers/dependent_matcher'
|
require 'shoulda/matchers/active_record/association_matchers/dependent_matcher'
|
||||||
|
|
|
@ -847,9 +847,9 @@ module Shoulda
|
||||||
(polymorphic? || class_exists?) &&
|
(polymorphic? || class_exists?) &&
|
||||||
foreign_key_exists? &&
|
foreign_key_exists? &&
|
||||||
class_name_correct? &&
|
class_name_correct? &&
|
||||||
|
join_table_correct? &&
|
||||||
autosave_correct? &&
|
autosave_correct? &&
|
||||||
conditions_correct? &&
|
conditions_correct? &&
|
||||||
join_table_exists? &&
|
|
||||||
validate_correct? &&
|
validate_correct? &&
|
||||||
touch_correct? &&
|
touch_correct? &&
|
||||||
submatchers_match?
|
submatchers_match?
|
||||||
|
@ -889,7 +889,8 @@ module Shoulda
|
||||||
end
|
end
|
||||||
|
|
||||||
def missing_options
|
def missing_options
|
||||||
[missing, failing_submatchers.map(&:missing_option)].flatten.join
|
missing_options = [missing, failing_submatchers.map(&:missing_option)]
|
||||||
|
missing_options.flatten.compact.join(', ')
|
||||||
end
|
end
|
||||||
|
|
||||||
def failing_submatchers
|
def failing_submatchers
|
||||||
|
@ -946,6 +947,19 @@ module Shoulda
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def join_table_correct?
|
||||||
|
if macro != :has_and_belongs_to_many || join_table_matcher.matches?(@subject)
|
||||||
|
true
|
||||||
|
else
|
||||||
|
@missing = join_table_matcher.failure_message
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def join_table_matcher
|
||||||
|
@join_table_matcher ||= AssociationMatchers::JoinTableMatcher.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
def class_exists?
|
def class_exists?
|
||||||
associated_class
|
associated_class
|
||||||
true
|
true
|
||||||
|
@ -980,16 +994,6 @@ module Shoulda
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def join_table_exists?
|
|
||||||
if macro != :has_and_belongs_to_many ||
|
|
||||||
model_class.connection.tables.include?(join_table)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
@missing = "join table #{join_table} doesn't exist"
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate_correct?
|
def validate_correct?
|
||||||
if option_verifier.correct_for_boolean?(:validate, options[:validate])
|
if option_verifier.correct_for_boolean?(:validate, options[:validate])
|
||||||
true
|
true
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
module Shoulda
|
||||||
|
module Matchers
|
||||||
|
module ActiveRecord
|
||||||
|
module AssociationMatchers
|
||||||
|
# @private
|
||||||
|
class JoinTableMatcher
|
||||||
|
attr_reader :association_matcher, :failure_message
|
||||||
|
alias :missing_option :failure_message
|
||||||
|
|
||||||
|
delegate :model_class, :join_table, :associated_class,
|
||||||
|
to: :association_matcher
|
||||||
|
|
||||||
|
delegate :connection, to: :model_class
|
||||||
|
|
||||||
|
def initialize(association_matcher)
|
||||||
|
@association_matcher = association_matcher
|
||||||
|
end
|
||||||
|
|
||||||
|
def matches?(subject)
|
||||||
|
join_table_exists? &&
|
||||||
|
join_table_has_correct_columns?
|
||||||
|
end
|
||||||
|
|
||||||
|
def join_table_exists?
|
||||||
|
if connection.tables.include?(join_table)
|
||||||
|
true
|
||||||
|
else
|
||||||
|
@failure_message = missing_table_message
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def join_table_has_correct_columns?
|
||||||
|
if missing_columns.empty?
|
||||||
|
true
|
||||||
|
else
|
||||||
|
@failure_message = missing_columns_message
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def missing_columns
|
||||||
|
@missing_columns ||= expected_join_table_columns.select do |key|
|
||||||
|
!actual_join_table_columns.include?(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def expected_join_table_columns
|
||||||
|
[
|
||||||
|
"#{model_class.name.underscore}_id",
|
||||||
|
"#{associated_class.name.underscore}_id"
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def actual_join_table_columns
|
||||||
|
connection.columns(join_table).map(&:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_table_message
|
||||||
|
"join table #{join_table} doesn't exist"
|
||||||
|
end
|
||||||
|
|
||||||
|
def missing_columns_message
|
||||||
|
missing = missing_columns.join(', ')
|
||||||
|
"join table #{join_table} missing #{column_label}: #{missing}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def column_label
|
||||||
|
if missing_columns.count > 1
|
||||||
|
'columns'
|
||||||
|
else
|
||||||
|
'column'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -712,7 +712,24 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
|
||||||
has_and_belongs_to_many :relatives
|
has_and_belongs_to_many :relatives
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(Person.new).not_to have_and_belong_to_many(:relatives)
|
expected_failure_message = "join table people_relatives doesn't exist"
|
||||||
|
|
||||||
|
expect do
|
||||||
|
expect(Person.new).to have_and_belong_to_many(:relatives)
|
||||||
|
end.to fail_with_message_including(expected_failure_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
define_model :people_relative, id: false, 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, relative_id')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects an association of the wrong type' do
|
it 'rejects an association of the wrong type' do
|
||||||
|
|
Loading…
Reference in New Issue