2014-01-23 18:07:36 +00:00
|
|
|
module Shoulda
|
2010-12-15 22:34:19 +00:00
|
|
|
module Matchers
|
2014-01-23 18:07:36 +00:00
|
|
|
module ActiveModel
|
|
|
|
# @private
|
|
|
|
class ValidationMatcher
|
Add ignoring_interference_by_writer to all matchers
`allow_value` matcher is, of course, concerned with setting values on a
particular attribute on a particular record, and then checking that the
record is valid after doing so. That comes with a caveat: if the
attribute is overridden in such a way so that the same value going into
the attribute isn't the same value coming out of it, then `allow_value`
will balk -- it'll say, "I can't do that because that changes how I
work."
That's all well and good, but what the attribute intentionally changes
incoming values? ActiveRecord's typecasting behavior, for instance,
would trigger such an exception. What if the developer needs a way to
get around this? This is where `ignoring_interference_by_writer` comes
into play. You can tack it on to the end of the matcher, and you're free
to go on your way.
So, prior to this commit you could already apply it to `allow_value`,
but now in this commit it also works on any other matcher.
But, one little thing: sometimes using this qualifier isn't going to
work. Perhaps you or something else actually *is* overriding the
attribute to change incoming values in a specific way, and perhaps the
value that comes out makes the record fail validation, and there's
nothing you can do about it. So in this case, even if you're using
`ignoring_interference_by_writer`, we want to inform you about what the
attribute is doing -- what the input and output was. And so we do.
2015-12-31 01:47:46 +00:00
|
|
|
include Qualifiers::IgnoringInterferenceByWriter
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
def initialize(attribute)
|
Add ignoring_interference_by_writer to all matchers
`allow_value` matcher is, of course, concerned with setting values on a
particular attribute on a particular record, and then checking that the
record is valid after doing so. That comes with a caveat: if the
attribute is overridden in such a way so that the same value going into
the attribute isn't the same value coming out of it, then `allow_value`
will balk -- it'll say, "I can't do that because that changes how I
work."
That's all well and good, but what the attribute intentionally changes
incoming values? ActiveRecord's typecasting behavior, for instance,
would trigger such an exception. What if the developer needs a way to
get around this? This is where `ignoring_interference_by_writer` comes
into play. You can tack it on to the end of the matcher, and you're free
to go on your way.
So, prior to this commit you could already apply it to `allow_value`,
but now in this commit it also works on any other matcher.
But, one little thing: sometimes using this qualifier isn't going to
work. Perhaps you or something else actually *is* overriding the
attribute to change incoming values in a specific way, and perhaps the
value that comes out makes the record fail validation, and there's
nothing you can do about it. So in this case, even if you're using
`ignoring_interference_by_writer`, we want to inform you about what the
attribute is doing -- what the input and output was. And so we do.
2015-12-31 01:47:46 +00:00
|
|
|
super
|
2010-12-15 22:34:19 +00:00
|
|
|
@attribute = attribute
|
2015-12-14 00:01:14 +00:00
|
|
|
@expects_strict = false
|
|
|
|
@subject = nil
|
|
|
|
@last_submatcher_run = nil
|
|
|
|
@expected_message = nil
|
|
|
|
@expects_custom_validation_message = false
|
2012-09-12 00:53:21 +00:00
|
|
|
end
|
|
|
|
|
Add ignoring_interference_by_writer to all matchers
`allow_value` matcher is, of course, concerned with setting values on a
particular attribute on a particular record, and then checking that the
record is valid after doing so. That comes with a caveat: if the
attribute is overridden in such a way so that the same value going into
the attribute isn't the same value coming out of it, then `allow_value`
will balk -- it'll say, "I can't do that because that changes how I
work."
That's all well and good, but what the attribute intentionally changes
incoming values? ActiveRecord's typecasting behavior, for instance,
would trigger such an exception. What if the developer needs a way to
get around this? This is where `ignoring_interference_by_writer` comes
into play. You can tack it on to the end of the matcher, and you're free
to go on your way.
So, prior to this commit you could already apply it to `allow_value`,
but now in this commit it also works on any other matcher.
But, one little thing: sometimes using this qualifier isn't going to
work. Perhaps you or something else actually *is* overriding the
attribute to change incoming values in a specific way, and perhaps the
value that comes out makes the record fail validation, and there's
nothing you can do about it. So in this case, even if you're using
`ignoring_interference_by_writer`, we want to inform you about what the
attribute is doing -- what the input and output was. And so we do.
2015-12-31 01:47:46 +00:00
|
|
|
def description
|
|
|
|
ValidationMatcher::BuildDescription.call(self, simple_description)
|
|
|
|
end
|
|
|
|
|
2013-03-04 03:34:38 +00:00
|
|
|
def on(context)
|
|
|
|
@context = context
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-09-12 00:53:21 +00:00
|
|
|
def strict
|
2015-12-14 00:01:14 +00:00
|
|
|
@expects_strict = true
|
2012-09-12 00:53:21 +00:00
|
|
|
self
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
def expects_strict?
|
|
|
|
@expects_strict
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
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
|
2015-12-14 00:01:14 +00:00
|
|
|
|
|
|
|
self
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
def expects_custom_validation_message?
|
|
|
|
@expects_custom_validation_message
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
2012-09-12 00:53:21 +00:00
|
|
|
|
Add ignoring_interference_by_writer to all matchers
`allow_value` matcher is, of course, concerned with setting values on a
particular attribute on a particular record, and then checking that the
record is valid after doing so. That comes with a caveat: if the
attribute is overridden in such a way so that the same value going into
the attribute isn't the same value coming out of it, then `allow_value`
will balk -- it'll say, "I can't do that because that changes how I
work."
That's all well and good, but what the attribute intentionally changes
incoming values? ActiveRecord's typecasting behavior, for instance,
would trigger such an exception. What if the developer needs a way to
get around this? This is where `ignoring_interference_by_writer` comes
into play. You can tack it on to the end of the matcher, and you're free
to go on your way.
So, prior to this commit you could already apply it to `allow_value`,
but now in this commit it also works on any other matcher.
But, one little thing: sometimes using this qualifier isn't going to
work. Perhaps you or something else actually *is* overriding the
attribute to change incoming values in a specific way, and perhaps the
value that comes out makes the record fail validation, and there's
nothing you can do about it. So in this case, even if you're using
`ignoring_interference_by_writer`, we want to inform you about what the
attribute is doing -- what the input and output was. And so we do.
2015-12-31 01:47:46 +00:00
|
|
|
def matches?(subject)
|
|
|
|
@subject = subject
|
|
|
|
false
|
2015-12-14 00:01:14 +00:00
|
|
|
end
|
2012-09-12 13:47:20 +00:00
|
|
|
|
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
|
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
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
|
2014-06-27 22:39:39 +00:00
|
|
|
end
|
2015-12-14 00:01:14 +00:00
|
|
|
end
|
2014-06-27 22:39:39 +00:00
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
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?
|
2015-12-14 00:01:14 +00:00
|
|
|
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,
|
2015-12-14 00:01:14 +00:00
|
|
|
indent: 2
|
|
|
|
)
|
|
|
|
end
|
2012-09-12 13:47:20 +00:00
|
|
|
end
|
2015-12-14 00:01:14 +00:00
|
|
|
end
|
2014-06-27 22:39:39 +00:00
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
protected
|
|
|
|
|
|
|
|
attr_reader :attribute, :context, :subject, :last_submatcher_run
|
|
|
|
|
|
|
|
def model
|
|
|
|
subject.class
|
2012-09-12 13:47:20 +00:00
|
|
|
end
|
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
def allows_value_of(value, message = nil, &block)
|
|
|
|
matcher = allow_value_matcher(value, message, &block)
|
|
|
|
run_allow_or_disallow_matcher(matcher)
|
|
|
|
end
|
2013-03-27 18:13:46 +00:00
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
def disallows_value_of(value, message = nil, &block)
|
|
|
|
matcher = disallow_value_matcher(value, message, &block)
|
|
|
|
run_allow_or_disallow_matcher(matcher)
|
|
|
|
end
|
2014-06-27 22:39:39 +00:00
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
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.'
|
2015-12-14 00:01:14 +00:00
|
|
|
)
|
|
|
|
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.'
|
2015-12-14 00:01:14 +00:00
|
|
|
)
|
|
|
|
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).
|
Add ignoring_interference_by_writer to all matchers
`allow_value` matcher is, of course, concerned with setting values on a
particular attribute on a particular record, and then checking that the
record is valid after doing so. That comes with a caveat: if the
attribute is overridden in such a way so that the same value going into
the attribute isn't the same value coming out of it, then `allow_value`
will balk -- it'll say, "I can't do that because that changes how I
work."
That's all well and good, but what the attribute intentionally changes
incoming values? ActiveRecord's typecasting behavior, for instance,
would trigger such an exception. What if the developer needs a way to
get around this? This is where `ignoring_interference_by_writer` comes
into play. You can tack it on to the end of the matcher, and you're free
to go on your way.
So, prior to this commit you could already apply it to `allow_value`,
but now in this commit it also works on any other matcher.
But, one little thing: sometimes using this qualifier isn't going to
work. Perhaps you or something else actually *is* overriding the
attribute to change incoming values in a specific way, and perhaps the
value that comes out makes the record fail validation, and there's
nothing you can do about it. So in this case, even if you're using
`ignoring_interference_by_writer`, we want to inform you about what the
attribute is doing -- what the input and output was. And so we do.
2015-12-31 01:47:46 +00:00
|
|
|
strict(expects_strict?).
|
|
|
|
ignoring_interference_by_writer(ignore_interference_by_writer)
|
2015-12-14 00:01:14 +00:00
|
|
|
|
|
|
|
yield matcher if block_given?
|
2014-06-27 22:39:39 +00:00
|
|
|
|
|
|
|
matcher
|
2013-03-27 18:13:46 +00:00
|
|
|
end
|
|
|
|
|
2015-12-14 00:01:14 +00:00
|
|
|
def run_allow_or_disallow_matcher(matcher)
|
|
|
|
@last_submatcher_run = matcher
|
|
|
|
matcher.matches?(subject)
|
2012-09-12 00:53:21 +00:00
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2012-03-23 14:50:45 +00:00
|
|
|
end
|