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
|
2015-09-30 18:51:01 +00:00
|
|
|
# The `allow_value` matcher (or its alias, `allow_values`) is used to
|
|
|
|
# ensure that an attribute is valid or invalid if set to one or more
|
|
|
|
# values.
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# Take this model for example:
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
|
|
|
# class UserProfile
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :website_url
|
|
|
|
#
|
|
|
|
# validates_format_of :website_url, with: URI.regexp
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# You can use `allow_value` to test one value at a time:
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UserProfile do
|
|
|
|
# it { should allow_value('http://foo.com').for(:website_url) }
|
|
|
|
# it { should allow_value('http://bar.com').for(:website_url) }
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2015-09-30 18:51:01 +00:00
|
|
|
# class UserProfileTest < ActiveSupport::TestCase
|
|
|
|
# should allow_value('http://foo.com').for(:website_url)
|
|
|
|
# should allow_value('http://bar.com').for(:website_url)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# You can also test multiple values in one go, if you like. In the
|
|
|
|
# positive sense, this makes an assertion that none of the values cause the
|
|
|
|
# record to be invalid. In the negative sense, this makes an assertion
|
|
|
|
# that none of the values cause the record to be valid:
|
|
|
|
#
|
2014-01-23 18:07:36 +00:00
|
|
|
# # RSpec
|
|
|
|
# describe UserProfile do
|
|
|
|
# it do
|
2015-09-30 18:51:01 +00:00
|
|
|
# should allow_values('http://foo.com', 'http://bar.com').
|
|
|
|
# for(:website_url)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# it do
|
|
|
|
# should_not allow_values('http://foo.com', 'buz').
|
2014-01-23 18:07:36 +00:00
|
|
|
# for(:website_url)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2014-01-23 18:07:36 +00:00
|
|
|
# class UserProfileTest < ActiveSupport::TestCase
|
2015-09-30 18:51:01 +00:00
|
|
|
# should allow_values('http://foo.com', 'http://bar.com/baz').
|
|
|
|
# for(:website_url)
|
|
|
|
#
|
|
|
|
# should_not allow_values('http://foo.com', 'buz').
|
2014-01-23 18:07:36 +00:00
|
|
|
# for(:website_url)
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# #### Caveats
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# When using `allow_value` or any matchers that depend on it, you may
|
|
|
|
# encounter a CouldNotSetAttributeError. This exception is raised if the
|
|
|
|
# matcher, in attempting to set a value on the attribute, detects that
|
|
|
|
# the value set is different from the value that the attribute returns
|
|
|
|
# upon reading it back.
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# This usually happens if the writer method (`foo=`, `bar=`, etc.) for
|
|
|
|
# that attribute has custom logic to ignore certain incoming values or
|
|
|
|
# change them in any way. Here are three examples we've seen:
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# * You're attempting to assert that an attribute should not allow nil,
|
|
|
|
# yet the attribute's writer method contains a conditional to do nothing
|
|
|
|
# if the attribute is set to nil:
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# class Foo
|
|
|
|
# include ActiveModel::Model
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# attr_reader :bar
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# def bar=(value)
|
|
|
|
# return if value.nil?
|
|
|
|
# @bar = value
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# describe Foo do
|
|
|
|
# it do
|
|
|
|
# foo = Foo.new
|
|
|
|
# foo.bar = "baz"
|
|
|
|
# # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
|
|
|
|
# expect(foo).not_to allow_value(nil).for(:bar)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2015-10-13 10:36:14 +00:00
|
|
|
# * You're attempting to assert that a numeric attribute should not allow
|
|
|
|
# a string that contains non-numeric characters, yet the writer method
|
|
|
|
# for that attribute strips out non-numeric characters:
|
2015-09-30 18:51:01 +00:00
|
|
|
#
|
|
|
|
# class Foo
|
|
|
|
# include ActiveModel::Model
|
|
|
|
#
|
|
|
|
# attr_reader :bar
|
|
|
|
#
|
|
|
|
# def bar=(value)
|
|
|
|
# @bar = value.gsub(/\D+/, '')
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# describe Foo do
|
|
|
|
# it do
|
|
|
|
# foo = Foo.new
|
|
|
|
# # This will raise a CouldNotSetAttributeError since `foo.bar` is now "123"
|
|
|
|
# expect(foo).not_to allow_value("abc123").for(:bar)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# * You're passing a value to `allow_value` that the model typecasts into
|
|
|
|
# another value:
|
|
|
|
#
|
|
|
|
# describe Foo do
|
|
|
|
# # Assume that `attr` is a string
|
|
|
|
# # This will raise a CouldNotSetAttributeError since `attr` typecasts `[]` to `"[]"`
|
|
|
|
# it { should_not allow_value([]).for(:attr) }
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# So when you encounter this exception, you have a couple of options:
|
|
|
|
#
|
|
|
|
# * If you understand the problem and wish to override this behavior to
|
|
|
|
# get around this exception, you can add the
|
|
|
|
# `ignoring_interference_by_writer` qualifier like so:
|
|
|
|
#
|
|
|
|
# it do
|
|
|
|
# should_not allow_value([]).
|
|
|
|
# for(:attr).
|
|
|
|
# ignoring_interference_by_writer
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# * Note, however, that the above option will not always cause the test to
|
|
|
|
# pass. In this case, this is telling you that you don't need to use
|
|
|
|
# `allow_value`, or quite possibly even the validation that you're
|
|
|
|
# testing altogether. In any case, we would probably make the argument
|
|
|
|
# that since it's clear that something is responsible for sanitizing
|
|
|
|
# incoming data before it's stored in your model, there's no need to
|
|
|
|
# ensure that sanitization places the model in a valid state, if such
|
|
|
|
# sanitization creates valid data. In terms of testing, the sanitization
|
|
|
|
# code should probably be tested, but not the effects of that
|
|
|
|
# sanitization on the validness of the model.
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
|
|
|
# #### Qualifiers
|
|
|
|
#
|
|
|
|
# ##### on
|
|
|
|
#
|
|
|
|
# Use `on` if your validation applies only under a certain context.
|
|
|
|
#
|
|
|
|
# class UserProfile
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :birthday_as_string
|
|
|
|
#
|
|
|
|
# validates_format_of :birthday_as_string,
|
|
|
|
# with: /^(\d+)-(\d+)-(\d+)$/,
|
|
|
|
# on: :create
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UserProfile do
|
|
|
|
# it do
|
|
|
|
# should allow_value('2013-01-01').
|
|
|
|
# for(:birthday_as_string).
|
|
|
|
# on(:create)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2014-01-23 18:07:36 +00:00
|
|
|
# class UserProfileTest < ActiveSupport::TestCase
|
|
|
|
# should allow_value('2013-01-01').
|
|
|
|
# for(:birthday_as_string).
|
|
|
|
# on(:create)
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# ##### with_message
|
|
|
|
#
|
|
|
|
# Use `with_message` if you are using a custom validation message.
|
|
|
|
#
|
|
|
|
# class UserProfile
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :state
|
|
|
|
#
|
|
|
|
# validates_format_of :state,
|
|
|
|
# with: /^(open|closed)$/,
|
|
|
|
# message: 'State must be open or closed'
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UserProfile do
|
|
|
|
# it do
|
|
|
|
# should allow_value('open', 'closed').
|
|
|
|
# for(:state).
|
|
|
|
# with_message('State must be open or closed')
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2014-01-23 18:07:36 +00:00
|
|
|
# class UserProfileTest < ActiveSupport::TestCase
|
|
|
|
# should allow_value('open', 'closed').
|
|
|
|
# for(:state).
|
|
|
|
# with_message('State must be open or closed')
|
|
|
|
# end
|
|
|
|
#
|
2015-03-01 17:48:07 +00:00
|
|
|
# Use `with_message` with a regexp to perform a partial match:
|
|
|
|
#
|
|
|
|
# class UserProfile
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :state
|
|
|
|
#
|
|
|
|
# validates_format_of :state,
|
|
|
|
# with: /^(open|closed)$/,
|
|
|
|
# message: 'State must be open or closed'
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UserProfile do
|
|
|
|
# it do
|
|
|
|
# should allow_value('open', 'closed').
|
|
|
|
# for(:state).
|
|
|
|
# with_message(/open or closed/)
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2015-03-01 17:48:07 +00:00
|
|
|
# class UserProfileTest < ActiveSupport::TestCase
|
|
|
|
# should allow_value('open', 'closed').
|
|
|
|
# for(:state).
|
|
|
|
# with_message(/open or closed/)
|
|
|
|
# end
|
|
|
|
#
|
2014-01-23 18:07:36 +00:00
|
|
|
# Use `with_message` with the `:against` option if the attribute the
|
|
|
|
# validation message is stored under is different from the attribute
|
2015-03-01 17:48:07 +00:00
|
|
|
# being validated:
|
2014-01-23 18:07:36 +00:00
|
|
|
#
|
|
|
|
# class UserProfile
|
|
|
|
# include ActiveModel::Model
|
|
|
|
# attr_accessor :sports_team
|
|
|
|
#
|
|
|
|
# validate :sports_team_must_be_valid
|
|
|
|
#
|
|
|
|
# private
|
|
|
|
#
|
|
|
|
# def sports_team_must_be_valid
|
|
|
|
# if sports_team !~ /^(Broncos|Titans)$/i
|
|
|
|
# self.errors.add :chosen_sports_team,
|
|
|
|
# 'Must be either a Broncos fan or a Titans fan'
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe UserProfile do
|
|
|
|
# it do
|
|
|
|
# should allow_value('Broncos', 'Titans').
|
|
|
|
# for(:sports_team).
|
|
|
|
# with_message('Must be either a Broncos or Titans fan',
|
|
|
|
# against: :chosen_sports_team
|
|
|
|
# )
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2014-01-23 18:07:36 +00:00
|
|
|
# class UserProfileTest < ActiveSupport::TestCase
|
|
|
|
# should allow_value('Broncos', 'Titans').
|
|
|
|
# for(:sports_team).
|
|
|
|
# with_message('Must be either a Broncos or Titans fan',
|
|
|
|
# against: :chosen_sports_team
|
|
|
|
# )
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 18:51:01 +00:00
|
|
|
# ##### ignoring_interference_by_writer
|
|
|
|
#
|
|
|
|
# Use `ignoring_interference_by_writer` if you've encountered a
|
|
|
|
# CouldNotSetAttributeError and wish to ignore it. Please read the Caveats
|
|
|
|
# section above for more information.
|
|
|
|
#
|
|
|
|
# class Address < ActiveRecord::Base
|
|
|
|
# # Address has a zip_code field which is a string
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# # RSpec
|
|
|
|
# describe Address do
|
|
|
|
# it do
|
|
|
|
# should_not allow_value([]).
|
|
|
|
# for(:zip_code).
|
|
|
|
# ignoring_interference_by_writer
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
2015-09-30 19:15:23 +00:00
|
|
|
# # Minitest (Shoulda)
|
2015-09-30 18:51:01 +00:00
|
|
|
# class AddressTest < ActiveSupport::TestCase
|
|
|
|
# should_not allow_value([]).
|
|
|
|
# for(:zip_code).
|
|
|
|
# ignoring_interference_by_writer
|
|
|
|
# end
|
|
|
|
#
|
2014-01-23 18:07:36 +00:00
|
|
|
# @return [AllowValueMatcher]
|
2010-12-15 22:34:19 +00:00
|
|
|
#
|
2010-09-05 15:33:32 +00:00
|
|
|
def allow_value(*values)
|
2012-03-23 14:10:08 +00:00
|
|
|
if values.empty?
|
2012-12-20 05:04:27 +00:00
|
|
|
raise ArgumentError, 'need at least one argument'
|
2012-03-23 14:10:08 +00:00
|
|
|
else
|
|
|
|
AllowValueMatcher.new(*values)
|
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
2015-09-30 18:51:01 +00:00
|
|
|
# @private
|
2015-03-30 21:43:16 +00:00
|
|
|
alias_method :allow_values, :allow_value
|
2010-12-15 22:34:19 +00:00
|
|
|
|
2014-01-23 18:07:36 +00:00
|
|
|
# @private
|
|
|
|
class AllowValueMatcher
|
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
|
|
|
# @private
|
|
|
|
class CouldNotSetAttributeError < Shoulda::Matchers::Error
|
|
|
|
def self.create(model, attribute, expected_value, actual_value)
|
|
|
|
super(
|
|
|
|
model: model,
|
|
|
|
attribute: attribute,
|
|
|
|
expected_value: expected_value,
|
|
|
|
actual_value: actual_value
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
attr_accessor :model, :attribute, :expected_value, :actual_value
|
|
|
|
|
|
|
|
def message
|
2015-10-07 05:10:51 +00:00
|
|
|
Shoulda::Matchers.word_wrap <<-MESSAGE
|
|
|
|
The allow_value matcher attempted to set :#{attribute} on #{model.name} to
|
|
|
|
#{expected_value.inspect}, but when the attribute was read back, it
|
|
|
|
had stored #{actual_value.inspect} instead.
|
|
|
|
|
|
|
|
This creates a problem because it means that the model is behaving in a way that
|
|
|
|
is interfering with the test -- there's a mismatch between the test that was
|
|
|
|
written and test that was actually run.
|
|
|
|
|
|
|
|
There are a couple of reasons why this could be happening:
|
|
|
|
|
|
|
|
* The writer method for :#{attribute} has been overridden and contains custom
|
|
|
|
logic to prevent certain values from being set or change which values are
|
|
|
|
stored.
|
|
|
|
* ActiveRecord is typecasting the incoming value.
|
|
|
|
|
|
|
|
Regardless, the fact you're seeing this message usually indicates a larger
|
|
|
|
problem. Please file an issue on the GitHub repo for shoulda-matchers,
|
|
|
|
including details about your model and the test you've written, and we can point
|
|
|
|
you in the right direction:
|
|
|
|
|
|
|
|
https://github.com/thoughtbot/shoulda-matchers/issues
|
|
|
|
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
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
include Helpers
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
attr_accessor :failure_message_preface
|
|
|
|
attr_reader :last_value_set
|
2013-03-28 18:48:45 +00:00
|
|
|
|
2010-09-05 15:33:32 +00:00
|
|
|
def initialize(*values)
|
2015-12-13 23:55:34 +00:00
|
|
|
@values_to_set = values
|
|
|
|
@options = {}
|
|
|
|
@after_setting_value_callback = -> {}
|
Tighten CouldNotSetAttributeError restriction
Why:
* Previously, `allow_value` would raise a CouldNotSetAttributeError
if the value being set didn't match the value the attribute had after
being set, but only if the attribute was being changed from nil to
non-nil or non-nil to nil.
* It turns out it doesn't matter which value you're trying to set the
attribute to -- if the attribute rejects that change it's confusing
either way. (In fact, I was recently bit by a case in which I was
trying to validate numericality of an attribute, where the writer
method for that attribute was overridden to ensure that the attribute
always stored a number and never contained non-number characters.
This ended up making the numericality validation useless, of
course -- but it caused confusion because the test acted in a way
I didn't expect.)
To satisfy the above:
* `allow_value` now raises a CouldNotSetAttributeError if the attribute
rejects the value being set in *any* way.
* However, add a `ignoring_interference_by_writer` qualifier so that
it is possible to manually override this behavior.
* Fix tests that are failing now because of this new change:
* Fix tests for allow_value matcher
* Fix tests for numericality matcher
* Remove tests for numericality matcher + integer column
* An integer column will typecast any non-integer value to an
integer.
* Because of the typecasting, our tests for the numericality matcher
against an integer column don't quite work, because we can't
really test what happens when the attribute is set to a
non-integer value. Now that `allow_value` is more strict, we're
getting a CouldNotSetAttributeError when attempting to do so.
* The tests mentioned were originally added to ensure that we are
handling RangeErrors that ActiveRecord used to emit. This doesn't
happen anymore, so the tests aren't necessary anymore either.
* Fix tests for acceptance matcher
* Fix tests for absence matcher
2015-09-26 05:10:00 +00:00
|
|
|
@ignoring_interference_by_writer = false
|
2015-12-13 23:55:34 +00:00
|
|
|
@expects_strict = false
|
|
|
|
@expects_custom_validation_message = false
|
|
|
|
@context = nil
|
|
|
|
|
|
|
|
@failure_message_preface = proc do
|
|
|
|
<<-PREFIX.strip_heredoc.strip
|
|
|
|
After setting :#{attribute_to_set} to #{last_value_set.inspect},
|
|
|
|
the matcher expected the #{model.name} to be
|
|
|
|
PREFIX
|
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def for(attribute)
|
2015-12-13 23:55:34 +00:00
|
|
|
@attribute_to_set = attribute
|
|
|
|
@attribute_to_check_message_against = attribute
|
2010-12-15 22:34:19 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2013-03-04 03:34:38 +00:00
|
|
|
def on(context)
|
2015-12-13 23:55:34 +00:00
|
|
|
if context.present?
|
|
|
|
@context = context
|
|
|
|
end
|
|
|
|
|
2013-03-04 03:34:38 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def with_message(message, given_options = {})
|
|
|
|
if message.present?
|
|
|
|
@expects_custom_validation_message = true
|
|
|
|
options[:expected_message] = message
|
|
|
|
options[:expected_message_values] = given_options.fetch(:values, {})
|
2014-04-26 15:10:17 +00:00
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
if given_options.key?(:against)
|
|
|
|
@attribute_to_check_message_against = given_options[:against]
|
|
|
|
end
|
2013-07-24 21:46:01 +00:00
|
|
|
end
|
2014-04-26 15:10:17 +00:00
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def expects_custom_validation_message?
|
|
|
|
@expects_custom_validation_message
|
|
|
|
end
|
|
|
|
|
|
|
|
def strict(expects_strict = true)
|
|
|
|
@expects_strict = expects_strict
|
2012-09-11 22:40:39 +00:00
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def expects_strict?
|
|
|
|
@expects_strict
|
|
|
|
end
|
|
|
|
|
Tighten CouldNotSetAttributeError restriction
Why:
* Previously, `allow_value` would raise a CouldNotSetAttributeError
if the value being set didn't match the value the attribute had after
being set, but only if the attribute was being changed from nil to
non-nil or non-nil to nil.
* It turns out it doesn't matter which value you're trying to set the
attribute to -- if the attribute rejects that change it's confusing
either way. (In fact, I was recently bit by a case in which I was
trying to validate numericality of an attribute, where the writer
method for that attribute was overridden to ensure that the attribute
always stored a number and never contained non-number characters.
This ended up making the numericality validation useless, of
course -- but it caused confusion because the test acted in a way
I didn't expect.)
To satisfy the above:
* `allow_value` now raises a CouldNotSetAttributeError if the attribute
rejects the value being set in *any* way.
* However, add a `ignoring_interference_by_writer` qualifier so that
it is possible to manually override this behavior.
* Fix tests that are failing now because of this new change:
* Fix tests for allow_value matcher
* Fix tests for numericality matcher
* Remove tests for numericality matcher + integer column
* An integer column will typecast any non-integer value to an
integer.
* Because of the typecasting, our tests for the numericality matcher
against an integer column don't quite work, because we can't
really test what happens when the attribute is set to a
non-integer value. Now that `allow_value` is more strict, we're
getting a CouldNotSetAttributeError when attempting to do so.
* The tests mentioned were originally added to ensure that we are
handling RangeErrors that ActiveRecord used to emit. This doesn't
happen anymore, so the tests aren't necessary anymore either.
* Fix tests for acceptance matcher
* Fix tests for absence matcher
2015-09-26 05:10:00 +00:00
|
|
|
def ignoring_interference_by_writer
|
|
|
|
@ignoring_interference_by_writer = true
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2014-07-24 04:03:51 +00:00
|
|
|
def _after_setting_value(&callback)
|
2015-12-13 23:55:34 +00:00
|
|
|
@after_setting_value_callback = callback
|
2014-04-16 06:24:02 +00:00
|
|
|
end
|
|
|
|
|
2010-12-15 22:34:19 +00:00
|
|
|
def matches?(instance)
|
2015-12-13 23:55:34 +00:00
|
|
|
@instance = instance
|
|
|
|
first_failing_value_and_validator.nil?
|
2014-10-22 07:18:54 +00:00
|
|
|
end
|
2013-03-26 22:16:04 +00:00
|
|
|
|
2014-10-22 07:18:54 +00:00
|
|
|
def does_not_match?(instance)
|
2015-12-13 23:55:34 +00:00
|
|
|
@instance = instance
|
|
|
|
first_passing_value_and_validator.nil?
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2013-12-24 11:24:27 +00:00
|
|
|
def failure_message
|
2015-12-13 23:55:34 +00:00
|
|
|
validator = first_failing_validator
|
|
|
|
message =
|
|
|
|
failure_message_preface.call +
|
|
|
|
' valid, but it was invalid instead,'
|
|
|
|
|
|
|
|
if validator.captured_validation_exception?
|
|
|
|
message << ' raising a validation exception with the message '
|
|
|
|
message << validator.validation_exception_message.inspect
|
|
|
|
message << '.'
|
|
|
|
else
|
|
|
|
message << " producing these validation errors:\n\n"
|
|
|
|
message << validator.all_formatted_validation_error_messages
|
|
|
|
end
|
|
|
|
|
|
|
|
Shoulda::Matchers.word_wrap(message)
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2013-12-24 11:24:27 +00:00
|
|
|
def failure_message_when_negated
|
2015-12-13 23:55:34 +00:00
|
|
|
validator = first_passing_validator
|
|
|
|
message = failure_message_preface.call + ' invalid'
|
|
|
|
|
|
|
|
if validator.type_of_message_matched?
|
|
|
|
if validator.has_messages?
|
|
|
|
message << ' and to'
|
|
|
|
|
|
|
|
if validator.captured_validation_exception?
|
|
|
|
message << ' raise a validation exception with message'
|
|
|
|
else
|
|
|
|
message << ' produce'
|
|
|
|
|
|
|
|
if expected_message.is_a?(Regexp)
|
|
|
|
message << ' a'
|
|
|
|
else
|
|
|
|
message << ' the'
|
|
|
|
end
|
|
|
|
|
|
|
|
message << ' validation error'
|
|
|
|
end
|
|
|
|
|
|
|
|
if expected_message.is_a?(Regexp)
|
|
|
|
message << ' matching'
|
|
|
|
end
|
|
|
|
|
|
|
|
message << " #{expected_message.inspect}"
|
|
|
|
|
|
|
|
unless validator.captured_validation_exception?
|
|
|
|
message << " on :#{attribute_to_check_message_against}"
|
|
|
|
end
|
|
|
|
|
|
|
|
message << '. The record was indeed invalid, but'
|
|
|
|
|
|
|
|
if validator.captured_validation_exception?
|
|
|
|
message << ' the exception message was '
|
|
|
|
message << validator.validation_exception_message.inspect
|
|
|
|
message << ' instead.'
|
|
|
|
else
|
|
|
|
message << " it produced these validation errors instead:\n\n"
|
|
|
|
message << validator.all_formatted_validation_error_messages
|
|
|
|
end
|
|
|
|
else
|
|
|
|
message << ', but it was valid instead.'
|
|
|
|
end
|
|
|
|
elsif validator.captured_validation_exception?
|
|
|
|
message << ' and to produce validation errors, but the record'
|
|
|
|
message << ' raised a validation exception instead.'
|
|
|
|
else
|
|
|
|
message << ' and to raise a validation exception, but the record'
|
|
|
|
message << ' produced validation errors instead.'
|
|
|
|
end
|
|
|
|
|
|
|
|
Shoulda::Matchers.word_wrap(message)
|
|
|
|
end
|
|
|
|
|
|
|
|
def simple_description
|
|
|
|
"allow :#{attribute_to_set} to be #{inspected_values_to_set}"
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def description
|
2015-12-13 23:55:34 +00:00
|
|
|
ValidationMatcher::BuildDescription.call(self, simple_description)
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def model
|
|
|
|
instance.class
|
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
protected
|
2013-11-22 20:46:59 +00:00
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
attr_reader(
|
|
|
|
:after_setting_value_callback,
|
|
|
|
:attribute_to_check_message_against,
|
|
|
|
:attribute_to_set,
|
|
|
|
:context,
|
|
|
|
:instance,
|
|
|
|
:options,
|
|
|
|
:values_to_set,
|
|
|
|
)
|
2014-10-22 07:18:54 +00:00
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
private
|
2015-01-21 22:46:26 +00:00
|
|
|
|
Tighten CouldNotSetAttributeError restriction
Why:
* Previously, `allow_value` would raise a CouldNotSetAttributeError
if the value being set didn't match the value the attribute had after
being set, but only if the attribute was being changed from nil to
non-nil or non-nil to nil.
* It turns out it doesn't matter which value you're trying to set the
attribute to -- if the attribute rejects that change it's confusing
either way. (In fact, I was recently bit by a case in which I was
trying to validate numericality of an attribute, where the writer
method for that attribute was overridden to ensure that the attribute
always stored a number and never contained non-number characters.
This ended up making the numericality validation useless, of
course -- but it caused confusion because the test acted in a way
I didn't expect.)
To satisfy the above:
* `allow_value` now raises a CouldNotSetAttributeError if the attribute
rejects the value being set in *any* way.
* However, add a `ignoring_interference_by_writer` qualifier so that
it is possible to manually override this behavior.
* Fix tests that are failing now because of this new change:
* Fix tests for allow_value matcher
* Fix tests for numericality matcher
* Remove tests for numericality matcher + integer column
* An integer column will typecast any non-integer value to an
integer.
* Because of the typecasting, our tests for the numericality matcher
against an integer column don't quite work, because we can't
really test what happens when the attribute is set to a
non-integer value. Now that `allow_value` is more strict, we're
getting a CouldNotSetAttributeError when attempting to do so.
* The tests mentioned were originally added to ensure that we are
handling RangeErrors that ActiveRecord used to emit. This doesn't
happen anymore, so the tests aren't necessary anymore either.
* Fix tests for acceptance matcher
* Fix tests for absence matcher
2015-09-26 05:10:00 +00:00
|
|
|
def ignoring_interference_by_writer?
|
|
|
|
@ignoring_interference_by_writer
|
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def value_matches?(value, validator)
|
|
|
|
@last_value_set = value
|
2014-10-22 07:18:54 +00:00
|
|
|
set_attribute(value)
|
2015-12-13 23:55:34 +00:00
|
|
|
!errors_match?(validator) && !any_range_error_occurred?(validator)
|
2014-10-22 07:18:54 +00:00
|
|
|
end
|
|
|
|
|
2015-01-21 22:46:26 +00:00
|
|
|
def set_attribute(value)
|
|
|
|
instance.__send__("#{attribute_to_set}=", value)
|
Tighten CouldNotSetAttributeError restriction
Why:
* Previously, `allow_value` would raise a CouldNotSetAttributeError
if the value being set didn't match the value the attribute had after
being set, but only if the attribute was being changed from nil to
non-nil or non-nil to nil.
* It turns out it doesn't matter which value you're trying to set the
attribute to -- if the attribute rejects that change it's confusing
either way. (In fact, I was recently bit by a case in which I was
trying to validate numericality of an attribute, where the writer
method for that attribute was overridden to ensure that the attribute
always stored a number and never contained non-number characters.
This ended up making the numericality validation useless, of
course -- but it caused confusion because the test acted in a way
I didn't expect.)
To satisfy the above:
* `allow_value` now raises a CouldNotSetAttributeError if the attribute
rejects the value being set in *any* way.
* However, add a `ignoring_interference_by_writer` qualifier so that
it is possible to manually override this behavior.
* Fix tests that are failing now because of this new change:
* Fix tests for allow_value matcher
* Fix tests for numericality matcher
* Remove tests for numericality matcher + integer column
* An integer column will typecast any non-integer value to an
integer.
* Because of the typecasting, our tests for the numericality matcher
against an integer column don't quite work, because we can't
really test what happens when the attribute is set to a
non-integer value. Now that `allow_value` is more strict, we're
getting a CouldNotSetAttributeError when attempting to do so.
* The tests mentioned were originally added to ensure that we are
handling RangeErrors that ActiveRecord used to emit. This doesn't
happen anymore, so the tests aren't necessary anymore either.
* Fix tests for acceptance matcher
* Fix tests for absence matcher
2015-09-26 05:10:00 +00:00
|
|
|
ensure_that_attribute_was_set!(value)
|
2015-07-23 22:19:47 +00:00
|
|
|
after_setting_value_callback.call
|
2015-01-21 22:46:26 +00:00
|
|
|
end
|
|
|
|
|
Tighten CouldNotSetAttributeError restriction
Why:
* Previously, `allow_value` would raise a CouldNotSetAttributeError
if the value being set didn't match the value the attribute had after
being set, but only if the attribute was being changed from nil to
non-nil or non-nil to nil.
* It turns out it doesn't matter which value you're trying to set the
attribute to -- if the attribute rejects that change it's confusing
either way. (In fact, I was recently bit by a case in which I was
trying to validate numericality of an attribute, where the writer
method for that attribute was overridden to ensure that the attribute
always stored a number and never contained non-number characters.
This ended up making the numericality validation useless, of
course -- but it caused confusion because the test acted in a way
I didn't expect.)
To satisfy the above:
* `allow_value` now raises a CouldNotSetAttributeError if the attribute
rejects the value being set in *any* way.
* However, add a `ignoring_interference_by_writer` qualifier so that
it is possible to manually override this behavior.
* Fix tests that are failing now because of this new change:
* Fix tests for allow_value matcher
* Fix tests for numericality matcher
* Remove tests for numericality matcher + integer column
* An integer column will typecast any non-integer value to an
integer.
* Because of the typecasting, our tests for the numericality matcher
against an integer column don't quite work, because we can't
really test what happens when the attribute is set to a
non-integer value. Now that `allow_value` is more strict, we're
getting a CouldNotSetAttributeError when attempting to do so.
* The tests mentioned were originally added to ensure that we are
handling RangeErrors that ActiveRecord used to emit. This doesn't
happen anymore, so the tests aren't necessary anymore either.
* Fix tests for acceptance matcher
* Fix tests for absence matcher
2015-09-26 05:10:00 +00:00
|
|
|
def ensure_that_attribute_was_set!(expected_value)
|
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
|
|
|
actual_value = instance.__send__(attribute_to_set)
|
|
|
|
|
Tighten CouldNotSetAttributeError restriction
Why:
* Previously, `allow_value` would raise a CouldNotSetAttributeError
if the value being set didn't match the value the attribute had after
being set, but only if the attribute was being changed from nil to
non-nil or non-nil to nil.
* It turns out it doesn't matter which value you're trying to set the
attribute to -- if the attribute rejects that change it's confusing
either way. (In fact, I was recently bit by a case in which I was
trying to validate numericality of an attribute, where the writer
method for that attribute was overridden to ensure that the attribute
always stored a number and never contained non-number characters.
This ended up making the numericality validation useless, of
course -- but it caused confusion because the test acted in a way
I didn't expect.)
To satisfy the above:
* `allow_value` now raises a CouldNotSetAttributeError if the attribute
rejects the value being set in *any* way.
* However, add a `ignoring_interference_by_writer` qualifier so that
it is possible to manually override this behavior.
* Fix tests that are failing now because of this new change:
* Fix tests for allow_value matcher
* Fix tests for numericality matcher
* Remove tests for numericality matcher + integer column
* An integer column will typecast any non-integer value to an
integer.
* Because of the typecasting, our tests for the numericality matcher
against an integer column don't quite work, because we can't
really test what happens when the attribute is set to a
non-integer value. Now that `allow_value` is more strict, we're
getting a CouldNotSetAttributeError when attempting to do so.
* The tests mentioned were originally added to ensure that we are
handling RangeErrors that ActiveRecord used to emit. This doesn't
happen anymore, so the tests aren't necessary anymore either.
* Fix tests for acceptance matcher
* Fix tests for absence matcher
2015-09-26 05:10:00 +00:00
|
|
|
if expected_value != actual_value && !ignoring_interference_by_writer?
|
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 CouldNotSetAttributeError.create(
|
|
|
|
instance.class,
|
|
|
|
attribute_to_set,
|
|
|
|
expected_value,
|
|
|
|
actual_value
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def errors_match?(validator)
|
|
|
|
validator.has_messages? &&
|
|
|
|
validator.type_of_message_matched? &&
|
|
|
|
errors_for_attribute_match?(validator)
|
2013-03-26 22:16:04 +00:00
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def errors_for_attribute_match?(validator)
|
|
|
|
matched_errors(validator).compact.any?
|
2012-09-11 22:40:39 +00:00
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def matched_errors(validator)
|
2012-09-11 22:40:39 +00:00
|
|
|
if expected_message
|
2015-12-13 23:55:34 +00:00
|
|
|
validator.messages.grep(expected_message)
|
2012-09-11 22:40:39 +00:00
|
|
|
else
|
2015-12-13 23:55:34 +00:00
|
|
|
validator.messages
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def any_range_error_occurred?(validator)
|
2015-01-21 22:46:26 +00:00
|
|
|
validator.captured_range_error?
|
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def inspected_values_to_set
|
|
|
|
if values_to_set.size > 1
|
|
|
|
values_to_set.map(&:inspect).to_sentence(
|
|
|
|
two_words_connector: " or ",
|
|
|
|
last_word_connector: ", or"
|
|
|
|
)
|
2012-03-23 14:10:08 +00:00
|
|
|
else
|
2015-12-13 23:55:34 +00:00
|
|
|
values_to_set.first.inspect
|
2012-03-23 14:10:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def expected_message
|
2013-03-28 18:48:45 +00:00
|
|
|
if options.key?(:expected_message)
|
|
|
|
if Symbol === options[:expected_message]
|
2012-09-12 00:53:21 +00:00
|
|
|
default_expected_message
|
2012-03-23 14:10:08 +00:00
|
|
|
else
|
2013-03-28 18:48:45 +00:00
|
|
|
options[:expected_message]
|
2012-03-23 14:10:08 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-09-12 00:53:21 +00:00
|
|
|
def default_expected_message
|
2015-12-13 23:55:34 +00:00
|
|
|
if expects_strict?
|
|
|
|
"#{human_attribute_name} #{default_attribute_message}"
|
|
|
|
else
|
|
|
|
default_attribute_message
|
|
|
|
end
|
2012-09-12 00:53:21 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def default_attribute_message
|
2013-02-14 22:25:46 +00:00
|
|
|
default_error_message(
|
2013-03-28 18:48:45 +00:00
|
|
|
options[:expected_message],
|
2014-04-26 15:10:17 +00:00
|
|
|
default_attribute_message_values
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def default_attribute_message_values
|
|
|
|
defaults = {
|
2014-01-17 20:20:44 +00:00
|
|
|
model_name: model_name,
|
|
|
|
instance: instance,
|
2014-10-15 06:13:17 +00:00
|
|
|
attribute: attribute_to_check_message_against,
|
2014-04-26 15:10:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defaults.merge(options[:expected_message_values])
|
2012-09-12 00:53:21 +00:00
|
|
|
end
|
|
|
|
|
2015-12-13 23:55:34 +00:00
|
|
|
def values_and_validators
|
|
|
|
@_values_and_validators ||= values_to_set.map do |value|
|
|
|
|
[value, build_validator]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_validator
|
|
|
|
validator = Validator.new(
|
|
|
|
instance,
|
|
|
|
attribute_to_check_message_against
|
|
|
|
)
|
|
|
|
validator.context = context
|
|
|
|
validator.expects_strict = expects_strict?
|
|
|
|
validator
|
|
|
|
end
|
|
|
|
|
|
|
|
def first_failing_value_and_validator
|
|
|
|
@_first_failing_value_and_validator ||=
|
|
|
|
values_and_validators.detect do |value, validator|
|
|
|
|
!value_matches?(value, validator)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def first_failing_validator
|
|
|
|
first_failing_value_and_validator[1]
|
|
|
|
end
|
|
|
|
|
|
|
|
def first_passing_value_and_validator
|
|
|
|
@_first_passing_value_and_validator ||=
|
|
|
|
values_and_validators.detect do |value, validator|
|
|
|
|
value_matches?(value, validator)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def first_passing_validator
|
|
|
|
first_passing_value_and_validator[1]
|
|
|
|
end
|
|
|
|
|
2012-03-23 14:10:08 +00:00
|
|
|
def model_name
|
2013-03-28 18:48:45 +00:00
|
|
|
instance.class.to_s.underscore
|
2012-03-23 14:10:08 +00:00
|
|
|
end
|
2015-12-13 23:55:34 +00:00
|
|
|
|
|
|
|
def human_attribute_name
|
|
|
|
instance.class.human_attribute_name(attribute_to_check_message_against)
|
|
|
|
end
|
2012-03-23 14:10:08 +00:00
|
|
|
end
|
2010-12-15 22:34:19 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|