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
|
|
|
|
#
|
|
|
|
# # Test::Unit
|
|
|
|
# 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
|
|
|
|
#
|
|
|
|
# # Test::Unit
|
|
|
|
# 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
|
|
|
#
|
2014-01-23 18:07:36 +00:00
|
|
|
# # Test::Unit
|
|
|
|
# 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
|
2010-12-15 22:34:19 +00:00
|
|
|
def with_message(message)
|
|
|
|
@expected_message = message if message
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def matches?(subject)
|
|
|
|
super(subject)
|
|
|
|
@expected_message ||= :blank
|
2014-04-16 06:24:02 +00:00
|
|
|
|
|
|
|
if secure_password_being_validated?
|
|
|
|
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
|
|
|
|
|
|
|
|
def description
|
|
|
|
"require #{@attribute} to be set"
|
|
|
|
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)
|
|
|
|
rescue ActiveModel::AllowValueMatcher::CouldNotSetAttributeError
|
|
|
|
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)
|
|
|
|
rescue ActiveModel::AllowValueMatcher::CouldNotSetAttributeError => error
|
|
|
|
error.actual_value.blank?
|
|
|
|
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
|