Instead of using
describe Foo do
# ...
end
use
RSpec.describe Foo, type: :model do
# ...
end
instead. This is not exactly official, as the former style still works,
but the latter style is highly suggested in RSpec documentation and the
like, so this is what people are used to.
[ci skip]
`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.
While attempting to add support for `ignoring_interference_by_writer` to
the confirmation matcher, I was noticing that there are two attributes
we are concerned with: the attribute under test, and the confirmation
attribute -- for instance, `password` and `password_confirmation`. The
way that the matcher works, `password_confirmation` is set first on the
record before `password` is set, and then the whole record is validated.
This is fine, but I also noticed that `allow_value` has a specific way
of setting attributes -- not only does it check whether the attribute
being set exists and fail properly if it is does not, but it also
raises a CouldNotSetAttribute error if the attribute changes incoming
values. This logic needs to be performed on both `password_confirmation`
as well as `password`.
With that in mind, `allow_value` now supports a `values_to_preset=`
writer method which allows one to assign additional attributes unrelated
to the one being tested prior to validation. This will be used by the
confirmation matcher in a future commit.
This means that `allow_value` now operates in two steps:
1. Set attributes unrelated to the test, raising an error if any of the
attributes do not exist on the model.
2. Set the attribute under test to one or more values, raising an error
if the attribute does not exist, then running validations on the
record, failing with an appropriate error message if the validations
fail.
Note that the second step is similar to the first, although there are
more things involved. To that end, `allow_value` has been completely
refactored so that the logic for setting and validating attributes
happens in other places. Specifically, the core logic to set an
attribute (and capture the results) is located in a new AttributeSetter
class.
Also, the CouldNotSetAttributeError class has been moved to a namespace
and renamed to AttributeChangedValueError.
Finally, this commit fixes DisallowValueMatcher so that it is the true
opposite of AllowValueMatcher: DVM#matches? calls AVM#does_not_match?
and DVM#does_not_match? calls AVM#matches?.
This is part of a collection of commits that aim to improve failure
messages across the board, in order to make matchers easier to debug
when something goes wrong.
* Have the failure message describe more clearly what the `allow_value`
matcher was trying to do when it failed.
* Make the description of the matcher more readable.
* For each value that `allow_value` sets, use a different Validator
instance. The matcher still changes state as it runs, but a future
commit will refactor this further.
* Merge StrictValidator back into Validator, and remove it. The way that
StrictValidator worked (as a module that was mixed into an instance of
Validator at runtime) was confusing, and there's really no need to
split out the logic anymore.
* Fix or fill in tests involving failure messages and descriptions.
This is part of a collection of commits that aim to improve failure
messages across the board, in order to make matchers easier to debug
when something goes wrong.
In this case, we're improving ValidationMatcher, which applies to most
matchers except for numericality.
* Reword the overall failure message so that it includes the failure
message for the last submatcher that failed.
* Extract code to build the description to BuildDescription. This code
now checks to see whether certain qualifiers have been specified and
includes information about them in the description, if present.
* Add a base implementation of `with_message` to ValidationMatcher.
* Add simple booleans to check whether `with_message` or `strict` have
been specified.
Active Record 4.2.1 and upper no more raises a `RangeError` on the
assign, only at database level [1].
This will remove all code related to handling of Active Record `RangeError`.
Also, this upgrades the internal dependency on Rails 4.2 to 4.2.3.
[1]: fed7d7cd7c
In Rails 4.2, ActiveRecord was changed such that if you attempt to set
an attribute to a value and that value is outside the range of the
column, then it will raise a RangeError. For instance, an integer column
with a limit of 2 (i.e. a smallint) only accepts values between -32768
and +32767.
This means that if you try to do any of these three things, a RangeError
could be raised:
* Use validate_numericality_of along with any of the comparison
submatchers and a value that sits on either side of the boundary.
* Use allow_value with a value that sits outside the range.
* Use validates_inclusion_of against an integer column. (Here we attempt
to set that column to a non-integer value to verify that the attribute
does not allow said value. That value is really a string version of a
large number, so if the column does not take large numbers then the
matcher could blow up.)
Ancillary changes in this commit:
* Remove ValidationMessageFinder and ExceptionMessageFinder in favor of
Validator, StrictValidator, and ValidatorWithCapturedRangeError.
* The allow_value matcher now uses an instance of Validator under the
hood. StrictValidator and/or ValidatorWithCapturedRangeError may be
mixed into the Validator object as needed.
Secondary author: Elliot Winkler <elliot.winkler@gmail.com>
This commit fixes `validate_uniqueness_of` when used with `scoped_to` so
that when one of the scope attributes is a polymorphic *_type attribute
and the model has another validation on the same attribute, the matcher
does not fail with an error.
As a part of the matching process, `validate_uniqueness_of` tries to
create two records that have the same values for each of the attributes
passed to `scoped_to`, except one of the attributes has a different
value. This way, the second record should be valid because it shouldn't
clash with the first one. It does this one attribute at a time.
Let's say the attribute in question is a polymorphic *_type attribute.
The value of this attribute is intended to be the name of a model as a
string. Let's assume the first record has a meaningful value for this
attribute already and we're trying to find a value for the second
record. In order to produce such a value, `validate_uniqueness_of`
generally calls #next (a common method in Ruby to generate a succeeding
version of an object sequentially) on the first object's corresponding
value. So in the case of a *_type attribute, since it's a string, it
would call #next on that string. For instance, "User" would become
"Uses".
You might have noticed a problem with this, which is "what if Uses is
not a valid model?" This is okay as long as there's nothing that is
trying to access the polymorphic association. Because as soon as this
happens, Rails will attempt to find a record using the polymorphic type
-- in other words, it will try to find the model that the *_type
attribute corresponds to. One of the ways this can happen is if the
*_type attribute in question has a validation on it itself.
Let's look at an example.
Given these models:
``` ruby
class User < ActiveRecord::Base
end
class Favorite < ActiveRecord::Base
belongs_to :favoriteable, polymorphic: true
validates :favoriteable, presence: true
validates :favoriteable_id, uniqueness: { scope: [:favoriteable_type] }
end
FactoryGirl.define do
factory :user
factory :favorite do
association :favoriteable, factory: :user
end
end
```
and the following test:
``` ruby
require 'rails_helper'
describe Favorite do
context 'validations' do
before { FactoryGirl.create(:favorite) }
it do
should validate_uniqueness_of(:favoriteable_id).
scoped_to(:favoriteable_type)
end
end
end
```
prior to this commit, the test would have failed with:
```
Failures:
1) Favorite validations should require case sensitive unique value for favoriteable_id scoped to favoriteable_type
Failure/Error: should validate_uniqueness_of(:favoriteable_id).
NameError:
uninitialized constant Uses
# ./spec/models/favorite_spec.rb:6:in `block (3 levels) in <top (required)>'
```
Here, a Favorite record is created where `favoriteable_type` is set to
"Uses", and then validations are run on that record. The presence
validation on `favoriteable_type` is run which tries to access a "Uses"
model. But that model doesn't exist, so the test raises an error.
Now `validates_uniqueness_of` will set the *_type attribute to a
meaningful value. It still does this by calling #next on the first
record's value, but then it makes a new model that is simply an alias
for the original model. Hence, in our example, Uses would become a model
that is aliased to User.
You can now use the following with validate_numericality_of:
* is_greater_than (corresponds to :greater_than)
* is_greater_than_or_equal_to (corresponds to :greater_than_or_equal_to)
* is_equal_to (corresponds to :equal_to)
* is_less_than (corresponds_to :less_than)
* is_less_than_or_equal_to (corresponds_to :less_than_or_equal_to)