Reject associations when class is not defined

This commit is contained in:
Chulki Lee 2014-01-10 09:25:14 -08:00 committed by Elliot Winkler
parent fcbb5c485e
commit f835cf439e
3 changed files with 100 additions and 0 deletions

View File

@ -1,5 +1,8 @@
# HEAD
* Association matchers now test that the model being referred to (either
implicitly or explicitly, using `:class_name`) actually exists.
# v 2.5.0
* Fix Rails/Test::Unit integration to ensure that the test case classes we are

View File

@ -160,6 +160,7 @@ module Shoulda # :nodoc:
@subject = subject
association_exists? &&
macro_correct? &&
class_exists? &&
foreign_key_exists? &&
class_name_correct? &&
conditions_correct? &&
@ -257,6 +258,14 @@ module Shoulda # :nodoc:
end
end
def class_exists?
associated_class
true
rescue NameError
@missing = "#{reflection.class_name} does not exist"
false
end
def conditions_correct?
if options.key?(:conditions)
if option_verifier.correct_for_relation_clause?(:conditions, options[:conditions])

View File

@ -30,6 +30,7 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
end
it 'accepts a polymorphic association' do
define_model :parent
define_model :child, parent_type: :string, parent_id: :integer do
belongs_to :parent, polymorphic: true
end
@ -100,6 +101,21 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
belonging_to_parent.should_not belong_to(:parent).class_name('TreeChild')
end
it 'rejects an association with non-existent implicit class name' do
belonging_to_non_existent_class(:child, :parent).should_not belong_to(:parent)
end
it 'rejects an association with non-existent explicit class name' do
belonging_to_non_existent_class(:child, :parent, :class_name => 'Parent').should_not belong_to(:parent)
end
it 'adds error message when rejecting an association with non-existent class' do
message = 'Expected Child to have a belongs_to association called parent (Parent2 does not exist)'
expect {
belonging_to_non_existent_class(:child, :parent, :class_name => 'Parent2').should belong_to(:parent)
}.to fail_with_message(message)
end
context 'an association with a :validate option' do
[false, true].each do |validate_value|
context "when the model has validate: #{validate_value}" do
@ -194,6 +210,12 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
belongs_to :parent, options
end.new
end
def belonging_to_non_existent_class(model_name, assoc_name, options = {})
define_model model_name, "#{assoc_name}_id" => :integer do
belongs_to assoc_name, options
end.new
end
end
context 'have_many' do
@ -343,6 +365,21 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
having_many_children.should_not have_many(:children).class_name('Node')
end
it 'rejects an association with non-existent implicit class name' do
having_many_non_existent_class(:parent, :children).should_not have_many(:children)
end
it 'rejects an association with non-existent explicit class name' do
having_many_non_existent_class(:parent, :children, :class_name => 'Child').should_not have_many(:children)
end
it 'adds error message when rejecting an association with non-existent class' do
message = 'Expected Parent to have a has_many association called children (Child2 does not exist)'
expect {
having_many_non_existent_class(:parent, :children, :class_name => 'Child2').should have_many(:children)
}.to fail_with_message(message)
end
context 'validate' do
it 'accepts when the :validate option matches' do
having_many_children(validate: false).should have_many(:children).validate(false)
@ -396,6 +433,12 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
end
end.new
end
def having_many_non_existent_class(model_name, assoc_name, options = {})
define_model model_name do
has_many assoc_name, options
end.new
end
end
context 'have_one' do
@ -500,6 +543,21 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
having_one_detail.should_not have_one(:detail).class_name('NotSet')
end
it 'rejects an association with non-existent implicit class name' do
having_one_non_existent(:pserson, :detail).should_not have_one(:detail)
end
it 'rejects an association with non-existent explicit class name' do
having_one_non_existent(:person, :detail, :class_name => 'Detail').should_not have_one(:detail)
end
it 'adds error message when rejecting an association with non-existent class' do
message = 'Expected Person to have a has_one association called detail (Detail2 does not exist)'
expect {
having_one_non_existent(:person, :detail, :class_name => 'Detail2').should have_one(:detail)
}.to fail_with_message(message)
end
it 'accepts an association with a through' do
define_model :detail
@ -551,6 +609,12 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
end
end.new
end
def having_one_non_existent(model_name, assoc_name, options = {})
define_model model_name do
has_one assoc_name, options
end.new
end
end
context 'have_and_belong_to_many' do
@ -630,6 +694,24 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
should_not have_and_belong_to_many(:relatives).class_name('PersonRelatives')
end
it 'rejects an association with non-existent implicit class name' do
having_and_belonging_to_many_non_existent_class(:person, :relatives).
should_not have_and_belong_to_many(:relatives)
end
it 'rejects an association with non-existent explicit class name' do
having_and_belonging_to_many_non_existent_class(:person, :relatives, :class_name => 'Relative')
.should_not have_and_belong_to_many(:relatives)
end
it 'adds error message when rejecting an association with non-existent class' do
message = 'Expected Person to have a has_and_belongs_to_many association called relatives (Relative2 does not exist)'
expect {
having_and_belonging_to_many_non_existent_class(:person, :relatives, :class_name => 'Relative2').
should have_and_belong_to_many(:relatives)
}.to fail_with_message(message)
end
context 'validate' do
it 'accepts when the :validate option matches' do
having_and_belonging_to_many_relatives(validate: false).
@ -660,6 +742,12 @@ describe Shoulda::Matchers::ActiveRecord::AssociationMatcher do
has_and_belongs_to_many :relatives
end.new
end
def having_and_belonging_to_many_non_existent_class(model_name, assoc_name, options = {})
define_model model_name do
has_and_belongs_to_many assoc_name, options
end.new
end
end
def define_association_with_conditions(model, macro, name, conditions, other_options={})