Add support for hbtm :join_table option

Rails supports declaring a `has_and_belongs_to_many` relationship with a
custom `:join_table` option and some developers want to assert that the
correct value is being used. Add `AssocationMatcher#join_table` to allow
developers to test the following:

1) That the :join_table option is being used for the relationship
2) That the *correct value* is being used
3) That the custom join table exists in the database
This commit is contained in:
Jacob Morris 2014-09-10 13:38:20 -06:00 committed by Elliot Winkler
parent f80b85fa86
commit af85ac1f0e
4 changed files with 114 additions and 39 deletions

View File

@ -72,6 +72,8 @@
* Add `on` qualifier to `validate_numericality_of`. (h/t [#356], [#358])
* Add `join_table` qualifier to `have_and_belong_to_many`. ([#556])
[#402]: https://github.com/thoughtbot/shoulda-matchers/pull/402
[#587]: https://github.com/thoughtbot/shoulda-matchers/pull/587
[#662]: https://github.com/thoughtbot/shoulda-matchers/pull/662
@ -86,6 +88,7 @@
[#693]: https://github.com/thoughtbot/shoulda-matchers/pull/693
[#356]: https://github.com/thoughtbot/shoulda-matchers/pull/356
[#358]: https://github.com/thoughtbot/shoulda-matchers/pull/358
[#556]: https://github.com/thoughtbot/shoulda-matchers/pull/556
# 2.8.0

View File

@ -823,7 +823,9 @@ module Shoulda
# @private
class AssociationMatcher
delegate :reflection, :model_class, :associated_class, :through?,
:join_table_name, :polymorphic?, to: :reflector
:polymorphic?, to: :reflector
attr_reader :name, :options
def initialize(macro, name)
@macro = macro
@ -906,6 +908,7 @@ module Shoulda
end
def join_table(join_table_name)
@options[:join_table_name] = join_table_name
self
end
@ -941,18 +944,22 @@ module Shoulda
submatchers_match?
end
protected
attr_reader :submatchers, :missing, :subject, :macro, :name, :options
def reflector
@reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
def join_table_name
options[:join_table_name] || reflector.join_table_name
end
def option_verifier
@option_verifier ||= AssociationMatchers::OptionVerifier.new(reflector)
end
protected
attr_reader :submatchers, :missing, :subject, :macro
def reflector
@reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
end
def add_submatcher(matcher)
@submatchers << matcher
end

View File

@ -8,8 +8,8 @@ module Shoulda
alias :missing_option :failure_message
delegate :model_class, :join_table_name, :associated_class,
to: :association_matcher
delegate :model_class, :join_table_name, :associated_class, :options,
:name, :option_verifier, to: :association_matcher
delegate :connection, to: :model_class
@ -19,10 +19,24 @@ module Shoulda
end
def matches?(subject)
join_table_exists? &&
join_table_option_correct? &&
join_table_exists? &&
join_table_has_correct_columns?
end
def join_table_option_correct?
if options.key?(:join_table_name)
if option_verifier.correct_for_string?(:join_table, options[:join_table_name])
true
else
@failure_message = "#{name} should use '#{options[:join_table_name]}' for :join_table option"
false
end
else
true
end
end
def join_table_exists?
if connection.tables.include?(join_table_name)
true

View File

@ -886,46 +886,97 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher, type: :model do
end.to fail_with_message_including('missing columns: person_id, relative_id')
end
it "rejects an association with a bad :join_table option" do
define_model :relative
join_table_name = 'people_and_their_families'
context 'when the association is declared with a :join_table option' do
it 'accepts when testing with the same :join_table option' do
join_table_name = 'people_and_their_families'
define_model :person do
has_and_belongs_to_many(
:relatives, join_table: join_table_name
)
define_model :relative
define_model :person do
has_and_belongs_to_many(:relatives, join_table: join_table_name)
end
create_table(join_table_name, id: false) do |t|
t.references :person
t.references :relative
end
expect(Person.new).
to have_and_belong_to_many(:relatives).
join_table(join_table_name)
end
create_table("people_relatives", id: false) do |t|
t.references :person
t.references :relative
it 'accepts even when not explicitly testing with a :join_table option' do
join_table_name = 'people_and_their_families'
define_model :relative
define_model :person do
has_and_belongs_to_many(:relatives,
join_table: join_table_name
)
end
create_table(join_table_name, id: false) do |t|
t.references :person
t.references :relative
end
expect(Person.new).to have_and_belong_to_many(:relatives)
end
expect do
expect(Person.new).to(
have_and_belong_to_many(:relatives).join_table(join_table_name)
it 'rejects when testing with a different :join_table option' do
join_table_name = 'people_and_their_families'
define_model :relative
define_model :person do
has_and_belongs_to_many(
:relatives,
join_table: join_table_name
)
end
create_table(join_table_name, id: false) do |t|
t.references :person
t.references :relative
end
assertion = lambda do
expect(Person.new).
to have_and_belong_to_many(:relatives).
join_table('family_tree')
end
expect(&assertion).to fail_with_message_including(
"relatives should use 'family_tree' for :join_table option"
)
end.to fail_with_message_including("#{join_table_name} doesn't exist")
end
end
it "accepts an association with a valid :join_table option" do
define_model :relative
join_table_name = 'people_and_their_families'
context 'when the association is not declared with a :join_table option' do
it 'rejects when testing with a :join_table option' do
define_model :relative
define_model :person do
has_and_belongs_to_many(
:relatives, join_table: join_table_name
define_model :person do
has_and_belongs_to_many(:relatives)
end
create_table('people_relatives', id: false) do |t|
t.references :person
t.references :relative
end
assertion = lambda do
expect(Person.new).
to have_and_belong_to_many(:relatives).
join_table('family_tree')
end
expect(&assertion).to fail_with_message_including(
"relatives should use 'family_tree' for :join_table option"
)
end
create_table(join_table_name, id: false) do |t|
t.references :person
t.references :relative
end
expect(Person.new).to(
have_and_belong_to_many(:relatives).join_table(join_table_name)
)
end
context 'using a custom foreign key' do