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
|
|
|
|
# The `validate_presence_of` matcher tests usage of the
|
|
|
|
# `validates_presence_of` validation.
|
|
|
|
#
|
|
|
|
# class Robot
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :arms
|
|
|
|
#
|
|
|
|
# validates_presence_of :arms
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe Robot do
|
|
|
|
# it { should validate_presence_of(:arms) }
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2014-01-23 18:07:36 +00:00
|
|
|
# class RobotTest < ActiveSupport::TestCase
|
|
|
|
# should validate_presence_of(:arms)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# #### Caveats
|
|
|
|
#
|
|
|
|
# Under Rails 4 and greater, if your model `has_secure_password` and you
|
|
|
|
# are validating presence of the password using a record whose password
|
|
|
|
# has already been set prior to calling the matcher, you will be
|
|
|
|
# instructed to use a record whose password is empty instead.
|
|
|
|
#
|
|
|
|
# For example, given this scenario:
|
|
|
|
#
|
|
|
|
# class User < ActiveRecord::Base
|
|
|
|
# has_secure_password validations: false
|
|
|
|
#
|
|
|
|
# validates_presence_of :password
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# describe User do
|
|
|
|
# subject { User.new(password: '123456') }
|
|
|
|
#
|
|
|
|
# it { should validate_presence_of(:password) }
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# the above test will raise an error like this:
|
|
|
|
#
|
|
|
|
# The validation failed because your User model declares
|
|
|
|
# `has_secure_password`, and `validate_presence_of` was called on a
|
|
|
|
# user which has `password` already set to a value. Please use a user
|
|
|
|
# with an empty `password` instead.
|
|
|
|
#
|
|
|
|
# This happens because `has_secure_password` itself overrides your model
|
|
|
|
# so that it is impossible to set `password` to nil. This means that it is
|
|
|
|
# impossible to test that setting `password` to nil places your model in
|
|
|
|
# an invalid state (which in turn means that the validation itself is
|
|
|
|
# unnecessary).
|
|
|
|
#
|
|
|
|
# #### Qualifiers
|
|
|
|
#
|
2014-12-04 21:49:50 +00:00
|
|
|
# ##### on
|
|
|
|
#
|
|
|
|
# Use `on` if your validation applies only under a certain context.
|
|
|
|
#
|
|
|
|
# class Robot
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :arms
|
|
|
|
#
|
|
|
|
# validates_presence_of :arms, on: :create
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe Robot do
|
|
|
|
# it { should validate_presence_of(:arms).on(:create) }
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2014-12-04 21:49:50 +00:00
|
|
|
# class RobotTest < ActiveSupport::TestCase
|
|
|
|
# should validate_presence_of(:arms).on(:create)
|
|
|
|
# end
|
|
|
|
#
|
2014-01-23 18:07:36 +00:00
|
|
|
# ##### with_message
|
|
|
|
#
|
|
|
|
# Use `with_message` if you are using a custom validation message.
|
|
|
|
#
|
|
|
|
# class Robot
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :legs
|
|
|
|
#
|
|
|
|
# validates_presence_of :legs, message: 'Robot has no legs'
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe Robot do
|
|
|
|
# it do
|
|
|
|
# should validate_presence_of(:legs).
|
|
|
|
# with_message('Robot has no legs')
|
|
|
|
# end
|
|
|
|
# end
|
2010-12-15 22:34:19 +00:00
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2014-01-23 18:07:36 +00:00
|
|
|
# class RobotTest < ActiveSupport::TestCase
|
|
|
|
# should validate_presence_of(:legs).
|
|
|
|
# with_message('Robot has no legs')
|
|
|
|
# end
|
2010-12-15 22:34:19 +00:00
|
|
|
#
|
2014-01-23 18:07:36 +00:00
|
|
|
# @return [ValidatePresenceOfMatcher]
|
2010-12-15 22:34:19 +00:00
|
|
|
#
|
|
|
|
def validate_presence_of(attr)
|
|
|
|
ValidatePresenceOfMatcher.new(attr)
|
|
|
|
end
|
|
|
|
|
2014-01-23 18:07:36 +00:00
|
|
|
# @private
|
|
|
|
class ValidatePresenceOfMatcher < ValidationMatcher
|
2015-12-13 23:48:24 +00:00
|
|
|
def initialize(attribute)
|
|
|
|
super
|
|
|
|
@expected_message = :blank
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def matches?(subject)
|
|
|
|
super(subject)
|
2014-04-16 06:24:02 +00:00
|
|
|
|
|
|
|
if secure_password_being_validated?
|
2016-01-05 05:45:48 +00:00
|
|
|
ignore_interference_by_writer.default_to(when: :blank?)
|
2014-04-16 06:24:02 +00:00
|
|
|
disallows_and_double_checks_value_of!(blank_value, @expected_message)
|
2013-11-22 20:46:59 +00:00
|
|
|
else
|
2015-09-30 04:28:59 +00:00
|
|
|
disallows_original_or_typecast_value?(blank_value, @expected_message)
|
2013-11-22 20:46:59 +00:00
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2015-12-13 23:48:24 +00:00
|
|
|
def simple_description
|
|
|
|
"validate that :#{@attribute} cannot be empty/falsy"
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2014-04-16 06:24:02 +00:00
|
|
|
def secure_password_being_validated?
|
|
|
|
defined?(::ActiveModel::SecurePassword) &&
|
|
|
|
@subject.class.ancestors.include?(::ActiveModel::SecurePassword::InstanceMethodsOnActivation) &&
|
|
|
|
@attribute == :password
|
|
|
|
end
|
|
|
|
|
|
|
|
def disallows_and_double_checks_value_of!(value, message)
|
allow_value: Raise error if attr sets value differently
`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.
2013-11-22 20:46:59 +00:00
|
|
|
disallows_value_of(value, message)
|
allow_value: pre-set attributes before validation
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?.
2015-12-19 17:11:01 +00:00
|
|
|
rescue ActiveModel::AllowValueMatcher::AttributeChangedValueError
|
allow_value: Raise error if attr sets value differently
`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.
2013-11-22 20:46:59 +00:00
|
|
|
raise ActiveModel::CouldNotSetPasswordError.create(@subject.class)
|
2014-04-16 06:24:02 +00:00
|
|
|
end
|
|
|
|
|
2015-09-30 04:28:59 +00:00
|
|
|
def disallows_original_or_typecast_value?(value, message)
|
|
|
|
disallows_value_of(blank_value, @expected_message)
|
|
|
|
end
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
def blank_value
|
|
|
|
if collection?
|
|
|
|
[]
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def collection?
|
2012-04-24 22:01:50 +00:00
|
|
|
if reflection
|
2010-12-15 22:34:19 +00:00
|
|
|
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
2012-04-24 22:01:50 +00:00
|
|
|
|
|
|
|
def reflection
|
|
|
|
@subject.class.respond_to?(:reflect_on_association) &&
|
|
|
|
@subject.class.reflect_on_association(@attribute)
|
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|