thoughtbot--shoulda-matchers/lib/shoulda/matchers/active_model/validation_matcher.rb

193 lines
4.8 KiB
Ruby
Raw Normal View History

module Shoulda
2010-12-15 22:34:19 +00:00
module Matchers
module ActiveModel
# @private
class ValidationMatcher
include Qualifiers::IgnoringInterferenceByWriter
2010-12-15 22:34:19 +00:00
def initialize(attribute)
super
2010-12-15 22:34:19 +00:00
@attribute = attribute
@expects_strict = false
@subject = nil
@last_submatcher_run = nil
@expected_message = nil
@expects_custom_validation_message = false
end
def description
ValidationMatcher::BuildDescription.call(self, simple_description)
end
2013-03-04 03:34:38 +00:00
def on(context)
@context = context
self
end
def allow_blank
options[:allow_blank] = true
self
end
def strict
@expects_strict = true
self
2010-12-15 22:34:19 +00:00
end
def expects_strict?
@expects_strict
2010-12-15 22:34:19 +00:00
end
def with_message(expected_message)
if expected_message
@expects_custom_validation_message = true
@expected_message = expected_message
2010-12-15 22:34:19 +00:00
end
self
2010-12-15 22:34:19 +00:00
end
def expects_custom_validation_message?
@expects_custom_validation_message
2010-12-15 22:34:19 +00:00
end
def matches?(subject)
@subject = subject
false
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
def does_not_match?(subject)
@subject = subject
true
end
def failure_message
overall_failure_message.dup.tap do |message|
if failure_reason.present?
message << "\n"
message << Shoulda::Matchers.word_wrap(
failure_reason,
indent: 2,
)
end
end
end
def failure_message_when_negated
overall_failure_message_when_negated.dup.tap do |message|
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
if failure_reason.present?
message << "\n"
message << Shoulda::Matchers.word_wrap(
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
failure_reason,
indent: 2,
)
end
end
end
protected
attr_reader :attribute, :context, :subject, :last_submatcher_run
def model
subject.class
end
def allows_value_of(value, message = nil, &block)
matcher = allow_value_matcher(value, message, &block)
run_allow_or_disallow_matcher(matcher)
end
def disallows_value_of(value, message = nil, &block)
matcher = disallow_value_matcher(value, message, &block)
run_allow_or_disallow_matcher(matcher)
end
def allow_value_matcher(value, message = nil, &block)
build_allow_or_disallow_value_matcher(
matcher_class: AllowValueMatcher,
value: value,
message: message,
&block
)
end
def disallow_value_matcher(value, message = nil, &block)
build_allow_or_disallow_value_matcher(
matcher_class: DisallowValueMatcher,
value: value,
message: message,
&block
)
end
def allow_blank_matches?
!expects_to_allow_blank? ||
blank_values.all? { |value| allows_value_of(value) }
end
def allow_blank_does_not_match?
expects_to_allow_blank? &&
blank_values.all? { |value| disallows_value_of(value) }
end
private
attr_reader :options
def overall_failure_message
Shoulda::Matchers.word_wrap(
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
"Expected #{model.name} to #{description}, but this could not be " +
'proved.',
)
end
def overall_failure_message_when_negated
Shoulda::Matchers.word_wrap(
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
"Expected #{model.name} not to #{description}, but this could " +
'not be proved.',
)
end
def failure_reason
last_submatcher_run.try(:failure_message)
end
def failure_reason_when_negated
last_submatcher_run.try(:failure_message_when_negated)
end
def build_allow_or_disallow_value_matcher(args)
matcher_class = args.fetch(:matcher_class)
value = args.fetch(:value)
message = args[:message]
matcher = matcher_class.new(value).
for(attribute).
with_message(message).
on(context).
strict(expects_strict?).
ignoring_interference_by_writer(ignore_interference_by_writer)
yield matcher if block_given?
matcher
end
def run_allow_or_disallow_matcher(matcher)
@last_submatcher_run = matcher
matcher.matches?(subject)
end
def expects_to_allow_blank?
options[:allow_blank]
end
def blank_values
['', ' ', "\n", "\r", "\t", "\f"]
end
2010-12-15 22:34:19 +00:00
end
end
end
2012-03-23 14:50:45 +00:00
end