2014-10-27 00:51:42 +00:00
|
|
|
|
require 'unit_spec_helper'
|
2011-10-16 17:49:58 +00:00
|
|
|
|
|
2014-12-24 21:37:04 +00:00
|
|
|
|
describe Shoulda::Matchers::ActiveModel::ValidateExclusionOfMatcher, type: :model do
|
2012-12-20 05:04:27 +00:00
|
|
|
|
context 'an attribute which must be excluded from a range' do
|
|
|
|
|
it 'accepts ensuring the correct range' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: 2..5)).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
to validate_exclusion_of(:attr).in_range(2..5)
|
2011-10-16 17:49:58 +00:00
|
|
|
|
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
|
|
|
|
it 'rejects if the given range spills past the top of the range in the validation' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: 2..5)).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
not_to validate_exclusion_of(:attr).in_range(2..6)
|
2011-10-16 17:49:58 +00:00
|
|
|
|
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
|
|
|
|
it 'rejects if the given range falls short of the top of the range in the validation' do
|
|
|
|
|
expect(validating_exclusion(in: 2..5)).
|
|
|
|
|
not_to validate_exclusion_of(:attr).in_range(2..4)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'rejects if the given range spills past the bottom of the range in the validation' do
|
|
|
|
|
expect(validating_exclusion(in: 2..5)).
|
|
|
|
|
not_to validate_exclusion_of(:attr).in_range(1..5)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'rejects if the given range falls short of the bottom of the range in the validation' do
|
|
|
|
|
expect(validating_exclusion(in: 2..5)).
|
|
|
|
|
not_to validate_exclusion_of(:attr).in_range(3..5)
|
|
|
|
|
end
|
|
|
|
|
|
2012-12-20 05:04:27 +00:00
|
|
|
|
it 'does not override the default message with a blank' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: 2..5)).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
to validate_exclusion_of(:attr).in_range(2..5).with_message(nil)
|
2011-10-16 17:49:58 +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
|
|
|
|
|
|
|
|
|
it_supports(
|
|
|
|
|
'ignoring_interference_by_writer',
|
|
|
|
|
tests: {
|
|
|
|
|
reject_if_qualified_but_changing_value_interferes: {
|
|
|
|
|
model_name: 'Example',
|
|
|
|
|
attribute_name: :attr,
|
|
|
|
|
changing_values_with: :next_value,
|
|
|
|
|
expected_message: <<-MESSAGE.strip
|
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 Example to validate that :attr lies outside the range ‹2› to
|
|
|
|
|
‹5›, but this could not be proved.
|
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
|
|
|
|
After setting :attr to ‹1› -- which was read back as ‹2› -- the
|
|
|
|
|
matcher expected the Example to be valid, but it was invalid instead,
|
|
|
|
|
producing these validation errors:
|
|
|
|
|
|
|
|
|
|
* attr: ["is reserved"]
|
|
|
|
|
|
|
|
|
|
As indicated in the message above, :attr seems to be changing certain
|
|
|
|
|
values as they are set, and this could have something to do with why
|
|
|
|
|
this test is failing. If you've overridden the writer method for this
|
|
|
|
|
attribute, then you may need to change it to make this test pass, or
|
|
|
|
|
do something else entirely.
|
|
|
|
|
MESSAGE
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
model_creator: :active_model
|
|
|
|
|
) do
|
|
|
|
|
def validation_matcher_scenario_args
|
|
|
|
|
super.deep_merge(validation_options: { in: 2..5 })
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def configure_validation_matcher(matcher)
|
|
|
|
|
matcher.in_range(2..5)
|
|
|
|
|
end
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
it 'fails when used in the negative' do
|
|
|
|
|
assertion = lambda do
|
|
|
|
|
expect(validating_exclusion(in: 2..5)).
|
|
|
|
|
not_to validate_exclusion_of(:attr).in_range(2..5)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
message = <<-MESSAGE
|
|
|
|
|
Expected Example not to validate that :attr lies outside the range ‹2›
|
|
|
|
|
to ‹5›, but this could not be proved.
|
|
|
|
|
After setting :attr to ‹6›, the matcher expected the Example to be
|
|
|
|
|
invalid, but it was valid instead.
|
|
|
|
|
MESSAGE
|
|
|
|
|
|
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
|
|
|
end
|
2011-10-16 17:49:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
2013-03-30 18:03:38 +00:00
|
|
|
|
context 'an attribute which must be excluded from a range with excluded end' do
|
|
|
|
|
it 'accepts ensuring the correct range' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: 2...5)).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
to validate_exclusion_of(:attr).in_range(2...5)
|
2013-03-30 18:03:38 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it 'rejects ensuring excluded value' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: 2...5)).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
not_to validate_exclusion_of(:attr).in_range(2...4)
|
2013-03-30 18:03:38 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2012-12-20 05:04:27 +00:00
|
|
|
|
context 'an attribute with a custom validation message' do
|
|
|
|
|
it 'accepts ensuring the correct range' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: 2..4, message: 'not good')).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
to validate_exclusion_of(:attr).in_range(2..4).with_message(/not good/)
|
2011-10-16 17:49:58 +00:00
|
|
|
|
end
|
2015-02-05 22:34:06 +00:00
|
|
|
|
|
|
|
|
|
it 'accepts ensuring the correct range with an interpolated variable in the message' do
|
|
|
|
|
matcher = validating_exclusion(in: 2..4, message: '%{value} is not good')
|
|
|
|
|
expect(matcher).
|
|
|
|
|
to validate_exclusion_of(:attr).
|
|
|
|
|
in_range(2..4).
|
|
|
|
|
with_message(/^[234] is not good$/)
|
|
|
|
|
end
|
2011-10-16 17:49:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
2012-12-20 05:04:27 +00:00
|
|
|
|
context 'an attribute with custom range validations' do
|
|
|
|
|
it 'accepts ensuring the correct range and messages' do
|
|
|
|
|
model = custom_validation do
|
|
|
|
|
if attr >= 2 && attr <= 5
|
|
|
|
|
errors.add(:attr, 'should be out of this range')
|
2011-10-16 17:49:58 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-05-10 08:59:21 +00:00
|
|
|
|
expect(model).to validate_exclusion_of(:attr).in_range(2..5).
|
2012-12-20 05:04:27 +00:00
|
|
|
|
with_message(/should be out of this range/)
|
2013-03-30 18:03:38 +00:00
|
|
|
|
|
|
|
|
|
model = custom_validation do
|
|
|
|
|
if attr >= 2 && attr <= 4
|
|
|
|
|
errors.add(:attr, 'should be out of this range')
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-05-10 08:59:21 +00:00
|
|
|
|
expect(model).to validate_exclusion_of(:attr).in_range(2...5).
|
2013-03-30 18:03:38 +00:00
|
|
|
|
with_message(/should be out of this range/)
|
2011-10-16 17:49:58 +00:00
|
|
|
|
end
|
2015-12-13 23:47:59 +00:00
|
|
|
|
|
|
|
|
|
it 'has correct description' do
|
|
|
|
|
matcher = validate_exclusion_of(:attr).in_range(1..10)
|
|
|
|
|
|
|
|
|
|
expect(matcher.description).to eq(
|
2015-12-22 07:00:51 +00:00
|
|
|
|
'validate that :attr lies outside the range ‹1› to ‹10›'
|
2015-12-13 23:47:59 +00:00
|
|
|
|
)
|
|
|
|
|
end
|
2011-10-16 17:49:58 +00:00
|
|
|
|
end
|
|
|
|
|
|
2012-12-20 05:04:27 +00:00
|
|
|
|
context 'an attribute which must be excluded from an array' do
|
|
|
|
|
it 'accepts with correct array' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: %w(one two))).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
to validate_exclusion_of(:attr).in_array(%w(one two))
|
2012-08-19 09:07:05 +00:00
|
|
|
|
end
|
|
|
|
|
|
2012-12-20 05:04:27 +00:00
|
|
|
|
it 'rejects when only part of array matches' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: %w(one two))).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
not_to validate_exclusion_of(:attr).in_array(%w(one wrong_value))
|
2012-08-19 09:07:05 +00:00
|
|
|
|
end
|
|
|
|
|
|
2012-12-20 05:04:27 +00:00
|
|
|
|
it 'rejects when array does not match at all' do
|
2014-01-17 22:01:43 +00:00
|
|
|
|
expect(validating_exclusion(in: %w(one two))).
|
2014-05-10 08:59:21 +00:00
|
|
|
|
not_to validate_exclusion_of(:attr).in_array(%w(cat dog))
|
2012-08-19 09:07:05 +00:00
|
|
|
|
end
|
|
|
|
|
|
2015-12-13 23:47:59 +00:00
|
|
|
|
context 'when there is one value' do
|
|
|
|
|
it 'has correct description' do
|
|
|
|
|
expect(validate_exclusion_of(:attr).in_array([true]).description).
|
2015-12-22 07:00:51 +00:00
|
|
|
|
to eq 'validate that :attr is not ‹true›'
|
2015-12-13 23:47:59 +00:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when there are two values' do
|
|
|
|
|
it 'has correct description' do
|
|
|
|
|
matcher = validate_exclusion_of(:attr).in_array([true, 'dog'])
|
|
|
|
|
|
|
|
|
|
expect(matcher.description).to eq(
|
2015-12-22 07:00:51 +00:00
|
|
|
|
'validate that :attr is neither ‹true› nor ‹"dog"›'
|
2015-12-13 23:47:59 +00:00
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context 'when there are three or more values' do
|
|
|
|
|
it 'has correct description' do
|
|
|
|
|
matcher = validate_exclusion_of(:attr).in_array([true, 'dog', 'cat'])
|
|
|
|
|
|
|
|
|
|
expect(matcher.description).to eq(
|
2015-12-22 07:00:51 +00:00
|
|
|
|
'validate that :attr is neither ‹true›, ‹"dog"›, nor ‹"cat"›'
|
2015-12-13 23:47:59 +00:00
|
|
|
|
)
|
|
|
|
|
end
|
2013-03-15 06:32:53 +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
|
|
|
|
it_supports(
|
|
|
|
|
'ignoring_interference_by_writer',
|
|
|
|
|
tests: {
|
|
|
|
|
reject_if_qualified_but_changing_value_interferes: {
|
|
|
|
|
model_name: 'Example',
|
|
|
|
|
attribute_name: :attr,
|
|
|
|
|
changing_values_with: :next_value,
|
|
|
|
|
expected_message: <<-MESSAGE.strip
|
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 Example to validate that :attr is neither ‹"one"› nor ‹"two"›,
|
|
|
|
|
but this could not be proved.
|
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
|
|
|
|
After setting :attr to ‹"one"› -- which was read back as ‹"onf"› --
|
|
|
|
|
the matcher expected the Example to be invalid, but it was valid
|
|
|
|
|
instead.
|
|
|
|
|
|
|
|
|
|
As indicated in the message above, :attr seems to be changing certain
|
|
|
|
|
values as they are set, and this could have something to do with why
|
|
|
|
|
this test is failing. If you've overridden the writer method for this
|
|
|
|
|
attribute, then you may need to change it to make this test pass, or
|
|
|
|
|
do something else entirely.
|
|
|
|
|
MESSAGE
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
model_creator: :active_model
|
|
|
|
|
) do
|
|
|
|
|
def validation_matcher_scenario_args
|
|
|
|
|
super.deep_merge(validation_options: { in: ['one', 'two'] })
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def configure_validation_matcher(matcher)
|
|
|
|
|
matcher.in_array(['one', 'two'])
|
|
|
|
|
end
|
|
|
|
|
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
|
|
|
|
it 'fails when used in the negative' do
|
|
|
|
|
assertion = lambda do
|
|
|
|
|
expect(validating_exclusion(in: %w(one two))).
|
|
|
|
|
not_to validate_exclusion_of(:attr).in_array(%w(one two))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
message = <<-MESSAGE
|
|
|
|
|
Expected Example not to validate that :attr is neither ‹"one"› nor
|
|
|
|
|
‹"two"›, but this could not be proved.
|
|
|
|
|
After setting :attr to ‹"two"›, the matcher expected the Example to be
|
|
|
|
|
valid, but it was invalid instead, producing these validation errors:
|
|
|
|
|
|
|
|
|
|
* attr: ["is reserved"]
|
|
|
|
|
MESSAGE
|
|
|
|
|
|
|
|
|
|
expect(&assertion).to fail_with_message(message)
|
|
|
|
|
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 define_model_validating_exclusion(options)
|
|
|
|
|
options = options.dup
|
|
|
|
|
column_type = options.delete(:column_type) { :string }
|
|
|
|
|
super options.merge(column_type: column_type)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def define_model_validating_exclusion(options)
|
|
|
|
|
options = options.dup
|
|
|
|
|
attribute_name = options.delete(:attribute_name) { :attr }
|
|
|
|
|
column_type = options.delete(:column_type) { :integer }
|
|
|
|
|
|
|
|
|
|
define_model(:example, attribute_name => column_type) do |model|
|
|
|
|
|
model.validates_exclusion_of(attribute_name, options)
|
2012-08-19 09:07:05 +00:00
|
|
|
|
end
|
2012-12-20 05:04:27 +00:00
|
|
|
|
end
|
2012-08-19 09:07:05 +00:00
|
|
|
|
|
2012-12-20 05:04:27 +00:00
|
|
|
|
def validating_exclusion(options)
|
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
|
|
|
|
define_model_validating_exclusion(options).new
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
alias_method :build_record_validating_exclusion, :validating_exclusion
|
|
|
|
|
|
|
|
|
|
def validation_matcher_scenario_args
|
|
|
|
|
super.deep_merge(matcher_name: :validate_exclusion_of)
|
2012-08-19 09:07:05 +00:00
|
|
|
|
end
|
2011-10-16 17:49:58 +00:00
|
|
|
|
end
|