Skip to content

Commit

Permalink
Add support for hbtm :join_table option
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Jacob Morris committed Sep 10, 2014
1 parent 4f9a0d7 commit d9cadb8
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 33 deletions.
7 changes: 6 additions & 1 deletion lib/shoulda/matchers/active_record/association_matcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ def have_and_belong_to_many(name)
# @private
class AssociationMatcher
delegate :reflection, :model_class, :associated_class, :through?,
:join_table_name, :polymorphic?, to: :reflector
:polymorphic?, to: :reflector

def initialize(macro, name)
@macro = macro
Expand Down Expand Up @@ -825,6 +825,7 @@ def touch(touch = true)
end

def join_table(join_table_name)
@options[:join_table_name] = join_table_name
self
end

Expand Down Expand Up @@ -965,6 +966,10 @@ def join_table_matcher
AssociationMatchers::JoinTableMatcher.new(self, reflector)
end

def join_table_name
options[:join_table_name] || reflector.join_table_name
end

def class_exists?
associated_class
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class JoinTableMatcher

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

Expand All @@ -19,10 +19,24 @@ def initialize(association_matcher, reflector)
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
Expand Down
83 changes: 54 additions & 29 deletions spec/shoulda/matchers/active_record/association_matcher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -732,46 +732,71 @@ def having_one_non_existent(model_name, assoc_name, options = {})
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 "declaring the association with a :join_table option" do

define_model :person do
has_and_belongs_to_many(
:relatives, join_table: join_table_name
it "rejects an association with a bad :join_table option" do
define_model :relative

define_model :person do
has_and_belongs_to_many(
:relatives,
join_table: "people_and_their_families"
)
end

create_table("people_and_their_families", id: false) do |t|
t.references :person
t.references :relative
end

expect do
expect(Person.new).to(
have_and_belong_to_many(:relatives).join_table("family_tree")
)
end.to fail_with_message_including(
"relatives should use 'family_tree' for :join_table option"
)
end

create_table("people_relatives", id: false) do |t|
t.references :person
t.references :relative
end
it "rejects an association with a missing :join_table option" do
define_model :relative

expect do
expect(Person.new).to(
have_and_belong_to_many(:relatives).join_table(join_table_name)
)
end.to fail_with_message_including("#{join_table_name} doesn't exist")
end
define_model :person do
has_and_belongs_to_many(:relatives)
end

it "accepts an association with a valid :join_table option" do
define_model :relative
join_table_name = 'people_and_their_families'
create_table("people_relatives", id: false) do |t|
t.references :person
t.references :relative
end

define_model :person do
has_and_belongs_to_many(
:relatives, join_table: join_table_name
expect do
expect(Person.new).to(
have_and_belong_to_many(:relatives).join_table("family_tree")
)
end.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)
)
it "accepts an association with a valid :join_table option" do
define_model :relative
join_table_name = 'people_and_their_families'

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
end

context 'using a custom foreign key' do
Expand Down

0 comments on commit d9cadb8

Please sign in to comment.