Fix validates_confirmation_of matcher so that it works under Rails 4

Rails 4 changed how validates_confirmation_of works so that the error
message is applied to the confirmation attribute, not the original
attribute.
This commit is contained in:
Elliot Winkler 2013-07-24 15:46:01 -06:00
parent 34c2219b8a
commit a026afb800
7 changed files with 93 additions and 17 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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