Why:
* Given a model with an attribute that typecasts its incoming values to
a collection type such as array or hash (and assuming that the default
value is empty), the presence matcher failed to run successfully.
Because we assert that the attribute `should_not allow_value(nil)`,
and since nil is being typecast to an empty array or hash,
`allow_value` was raising CouldNotSetAttributeError.
To satisfy the above:
* Rescue CouldNotSetAttributeError; if it occurs, assert that the actual
value of the attribute is blank instead of the given value.
`allow_value` will now raise a CouldNotSetAttribute error if the
attribute in question cannot be changed from a non-nil value to a nil
value, or vice versa. In other words, these are the exact cases in which
the error will occur:
* If you're testing whether the attribute allows `nil`, but the
attribute detects and ignores nil. (For instance, you have a model
that `has_secure_password`. This will add a #password= method to your
model that is defined in a such a way that you cannot clear the
password by setting it to nil -- nothing happens.)
* If you're testing whether the attribute allows a non-nil value, but
the attribute fails to set that value. (For instance, you have an
ActiveRecord model. If ActiveRecord cannot typecast the value in the
context of the column, then it will do nothing, and the attribute will be
effectively set to nil.)
What's the reasoning behind this change? Simply put, if you are assuming
that the attribute is changing but in fact it is not, then the test
you're writing isn't the test that actually gets run. We feel that this
is dishonest and produces an invalid test.
It seems that adding a restriction to allow_value to raise
CouldNotClearAttribute if nil cannot be set on the attribute in question
broke a lot of people's tests. Really the only reason we added this was
for validate_presence_of -- it rescued CouldNotClearAttribute and
re-raised it as CouldNotSetPasswordError, but only for passwords and
only if has_secure_password was being used. So, I've figured out another
way of performing this check inside of validate_presence_of only to
prevent tests that have nothing to do with from breaking unnecessarily.
If you have a model that declares `has_secure_password` and you also
have a presence validation on the password, and you write a test against
this validation using an instance of your model where the password is
already set, then your test will fail. This is because
has_secure_password (at least on Rails 4) defines #password= such that
if it is given nil, then the password will not be overwritten with nil.
This interferes with how our validate_presence_of matcher works.
Unfortunately there is not a great way to get around this (using
\#write_attribute won't work, either). So in this case we raise a
helpful error message that instructs the user to use an empty record
against `validates_presence_of`.