diff --git a/lib/shoulda/matchers/active_model/allow_value_matcher.rb b/lib/shoulda/matchers/active_model/allow_value_matcher.rb index b0f54f36..24b91d77 100644 --- a/lib/shoulda/matchers/active_model/allow_value_matcher.rb +++ b/lib/shoulda/matchers/active_model/allow_value_matcher.rb @@ -30,6 +30,7 @@ module Shoulda # :nodoc: class AllowValueMatcher # :nodoc: include Helpers + attr_accessor :attribute_with_message attr_accessor :options def initialize(*values) @@ -39,7 +40,8 @@ module Shoulda # :nodoc: end def for(attribute) - self.attribute = attribute + self.attribute_to_set = attribute + self.attribute_to_check_message_against = attribute self end @@ -48,8 +50,11 @@ module Shoulda # :nodoc: self end - def with_message(message) + def with_message(message, options={}) self.options[:expected_message] = message + if options.key?(:against) + self.attribute_to_check_message_against = options[:against] + end self end @@ -63,7 +68,7 @@ module Shoulda # :nodoc: values_to_match.none? do |value| self.value = value - instance.send("#{attribute}=", value) + instance.send("#{attribute_to_set}=", value) errors_match? end end @@ -83,7 +88,8 @@ module Shoulda # :nodoc: private attr_accessor :values_to_match, :message_finder_factory, - :instance, :attribute, :context, :value, :matched_error + :instance, :attribute_to_set, :attribute_to_check_message_against, + :context, :value, :matched_error def errors_match? has_messages? && errors_for_attribute_match? @@ -119,7 +125,7 @@ module Shoulda # :nodoc: def expectation includes_expected_message = expected_message ? "to include #{expected_message.inspect}" : '' - [error_source, includes_expected_message, "when #{attribute} is set to #{value.inspect}"].join(' ') + [error_source, includes_expected_message, "when #{attribute_to_set} is set to #{value.inspect}"].join(' ') end def error_source @@ -157,7 +163,7 @@ module Shoulda # :nodoc: options[:expected_message], :model_name => model_name, :instance => instance, - :attribute => attribute + :attribute => attribute_to_set ) end @@ -166,7 +172,7 @@ module Shoulda # :nodoc: end def message_finder - message_finder_factory.new(instance, attribute, context) + message_finder_factory.new(instance, attribute_to_check_message_against, context) end end end diff --git a/lib/shoulda/matchers/active_model/disallow_value_matcher.rb b/lib/shoulda/matchers/active_model/disallow_value_matcher.rb index aa95cedf..99895121 100644 --- a/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +++ b/lib/shoulda/matchers/active_model/disallow_value_matcher.rb @@ -20,8 +20,8 @@ module Shoulda # :nodoc: self end - def with_message(message) - @allow_matcher.with_message(message) + def with_message(message, options={}) + @allow_matcher.with_message(message, options) self end diff --git a/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb b/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb index 1fc6204a..e6278964 100644 --- a/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +++ b/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb @@ -13,9 +13,11 @@ module Shoulda # :nodoc: class ValidateConfirmationOfMatcher < ValidationMatcher # :nodoc: include Helpers + attr_reader :attribute, :confirmation_attribute + def initialize(attribute) @attribute = attribute - @confirmation = "#{attribute}_confirmation" + @confirmation_attribute = "#{attribute}_confirmation" end def with_message(message) @@ -24,7 +26,7 @@ module Shoulda # :nodoc: end def description - "require #{@confirmation} to match #{@attribute}" + "require #{@confirmation_attribute} to match #{@attribute}" end def matches?(subject) @@ -40,25 +42,35 @@ module Shoulda # :nodoc: def disallows_different_value set_confirmation('some value') - disallows_value_of('different value', @message) + disallows_value_of('different value') do |matcher| + matcher.with_message(@message, against: error_attribute) + end end def allows_same_value set_confirmation('same value') - allows_value_of('same value', @message) + allows_value_of('same value') do |matcher| + matcher.with_message(@message, against: error_attribute) + end end def allows_missing_confirmation set_confirmation(nil) - allows_value_of('any value', @message) + allows_value_of('any value') do |matcher| + matcher.with_message(@message, against: error_attribute) + end end def set_confirmation(val) - setter = :"#{@confirmation}=" + setter = :"#{@confirmation_attribute}=" if @subject.respond_to?(setter) @subject.send(setter, val) end end + + def error_attribute + RailsShim.validates_confirmation_of_error_attribute(self) + end end end end diff --git a/lib/shoulda/matchers/active_model/validation_matcher.rb b/lib/shoulda/matchers/active_model/validation_matcher.rb index c97ae0f8..89684ae7 100644 --- a/lib/shoulda/matchers/active_model/validation_matcher.rb +++ b/lib/shoulda/matchers/active_model/validation_matcher.rb @@ -30,8 +30,9 @@ module Shoulda # :nodoc: private - def allows_value_of(value, message = nil) + def allows_value_of(value, message = nil, &block) allow = allow_value_matcher(value, message) + yield allow if block_given? if allow.matches?(@subject) @failure_message_for_should_not = allow.failure_message_for_should_not @@ -42,8 +43,9 @@ module Shoulda # :nodoc: end end - def disallows_value_of(value, message = nil) + def disallows_value_of(value, message = nil, &block) disallow = disallow_value_matcher(value, message) + yield disallow if block_given? if disallow.matches?(@subject) @failure_message_for_should_not = disallow.failure_message_for_should_not diff --git a/lib/shoulda/matchers/rails_shim.rb b/lib/shoulda/matchers/rails_shim.rb index ee39007a..6810ec55 100644 --- a/lib/shoulda/matchers/rails_shim.rb +++ b/lib/shoulda/matchers/rails_shim.rb @@ -25,6 +25,14 @@ module Shoulda # :nodoc: end end + def self.validates_confirmation_of_error_attribute(matcher) + if rails_major_version == 4 + matcher.confirmation_attribute + else + matcher.attribute + end + end + def self.rails_major_version Rails::VERSION::MAJOR end diff --git a/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb b/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb index 40652004..67f94de7 100644 --- a/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +++ b/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb @@ -56,6 +56,30 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher do end end + context 'an attribute where the message occurs on another attribute' do + it 'allows a good value' do + record_with_custom_validation.should \ + allow_value('good value').for(:attr).with_message(/some message/, :against => :attr2) + end + + it 'rejects a bad value' do + record_with_custom_validation.should_not \ + allow_value('bad value').for(:attr).with_message(/some message/, :against => :attr2) + end + + def record_with_custom_validation + define_model :example, :attr => :string, :attr2 => :string do + validate :custom_validation + + def custom_validation + if self[:attr] != 'good value' + self.errors[:attr2] << 'some message' + end + end + end.new + end + end + context "an attribute with a context-dependent validation" do context "without the validation context" do it "allows a bad value" do diff --git a/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb b/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb index c06a2a19..85be785b 100644 --- a/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +++ b/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb @@ -55,6 +55,30 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher do end end + context 'an attribute where the message occurs on another attribute' do + it 'matches if the message is correct but the value is not' do + record_with_custom_validation.should \ + matcher('bad value').for(:attr).with_message(/some message/, :against => :attr2) + end + + it 'does not match if the value and message are both correct' do + record_with_custom_validation.should_not \ + matcher('good value').for(:attr).with_message(/some message/, :against => :attr2) + end + + def record_with_custom_validation + define_model :example, :attr => :string, :attr2 => :string do + validate :custom_validation + + def custom_validation + if self[:attr] != 'good value' + self.errors[:attr2] << 'some message' + end + end + end.new + end + end + def matcher(value) described_class.new(value) end