Handle RangeErrors emitted now in ActiveRecord 4.2
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.
This commit is contained in:
parent
1766a09299
commit
9ba21381d7
10
NEWS.md
10
NEWS.md
|
@ -8,7 +8,17 @@
|
|||
the association has (or has not) been declared with *any* dependent option.
|
||||
([#631])
|
||||
|
||||
* Fix `allow_value`, `validate_numericality_of` and `validate_inclusion_of` so
|
||||
that they handle RangeErrors emitted from ActiveRecord 4.2. These exceptions
|
||||
arise whenever we attempt to set an attribute using a value that lies outside
|
||||
the range of the column (assuming the column is an integer). RangeError is now
|
||||
treated specially, failing the test instead of bubbling up as an error.
|
||||
([#634], [#637], [#642])
|
||||
|
||||
[#631]: https://github.com/thoughtbot/shoulda-matchers/pull/631
|
||||
[#634]: https://github.com/thoughtbot/shoulda-matchers/pull/634
|
||||
[#637]: https://github.com/thoughtbot/shoulda-matchers/pull/637
|
||||
[#642]: https://github.com/thoughtbot/shoulda-matchers/pull/642
|
||||
|
||||
# 2.8.0.rc1
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
require 'shoulda/matchers/active_model/helpers'
|
||||
require 'shoulda/matchers/active_model/validation_matcher'
|
||||
require 'shoulda/matchers/active_model/validation_message_finder'
|
||||
require 'shoulda/matchers/active_model/exception_message_finder'
|
||||
require 'shoulda/matchers/active_model/validator'
|
||||
require 'shoulda/matchers/active_model/strict_validator'
|
||||
require 'shoulda/matchers/active_model/validator_with_captured_range_error'
|
||||
require 'shoulda/matchers/active_model/allow_value_matcher'
|
||||
require 'shoulda/matchers/active_model/disallow_value_matcher'
|
||||
require 'shoulda/matchers/active_model/validate_length_of_matcher'
|
||||
|
|
|
@ -177,9 +177,9 @@ module Shoulda
|
|||
|
||||
def initialize(*values)
|
||||
self.values_to_match = values
|
||||
self.message_finder_factory = ValidationMessageFinder
|
||||
self.options = {}
|
||||
self.after_setting_value_callback = -> {}
|
||||
self.validator = Validator.new
|
||||
end
|
||||
|
||||
def for(attribute)
|
||||
|
@ -189,7 +189,7 @@ module Shoulda
|
|||
end
|
||||
|
||||
def on(context)
|
||||
@context = context
|
||||
validator.context = context
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -205,7 +205,7 @@ module Shoulda
|
|||
end
|
||||
|
||||
def strict
|
||||
self.message_finder_factory = ExceptionMessageFinder
|
||||
validator.strict = true
|
||||
self
|
||||
end
|
||||
|
||||
|
@ -215,16 +215,18 @@ module Shoulda
|
|||
|
||||
def matches?(instance)
|
||||
self.instance = instance
|
||||
validator.record = instance
|
||||
|
||||
values_to_match.none? do |value|
|
||||
validator.reset
|
||||
self.value = value
|
||||
set_value(value)
|
||||
errors_match?
|
||||
set_attribute(value)
|
||||
errors_match? || any_range_error_occurred?
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message
|
||||
"Did not expect #{expectation},\ngot error: #{matched_error}"
|
||||
"Did not expect #{expectation},\ngot#{error_description}"
|
||||
end
|
||||
alias failure_message_for_should failure_message
|
||||
|
||||
|
@ -234,26 +236,45 @@ module Shoulda
|
|||
alias failure_message_for_should_not failure_message_when_negated
|
||||
|
||||
def description
|
||||
message_finder.allow_description(allowed_values)
|
||||
validator.allow_description(allowed_values)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_accessor :values_to_match, :message_finder_factory,
|
||||
:instance, :attribute_to_set, :attribute_to_check_message_against,
|
||||
:context, :value, :matched_error, :after_setting_value_callback
|
||||
attr_reader :attribute_to_check_message_against
|
||||
attr_accessor :values_to_match, :instance, :attribute_to_set, :value,
|
||||
:matched_error, :after_setting_value_callback, :validator
|
||||
|
||||
def set_value(value)
|
||||
instance.__send__("#{attribute_to_set}=", value)
|
||||
def attribute_to_check_message_against=(attribute)
|
||||
@attribute_to_check_message_against = attribute
|
||||
validator.attribute = attribute
|
||||
end
|
||||
|
||||
def set_attribute(value)
|
||||
set_attribute_ignoring_range_errors(value)
|
||||
after_setting_value_callback.call
|
||||
end
|
||||
|
||||
def set_attribute_ignoring_range_errors(value)
|
||||
instance.__send__("#{attribute_to_set}=", value)
|
||||
rescue RangeError => exception
|
||||
# Have to reset the attribute so that we don't get a RangeError the
|
||||
# next time we attempt to write the attribute (ActiveRecord seems to
|
||||
# set the attribute to the "bad" value anyway)
|
||||
reset_attribute
|
||||
validator.capture_range_error(exception)
|
||||
end
|
||||
|
||||
def reset_attribute
|
||||
instance.send(:raw_write_attribute, attribute_to_set, nil)
|
||||
end
|
||||
|
||||
def errors_match?
|
||||
has_messages? && errors_for_attribute_match?
|
||||
end
|
||||
|
||||
def has_messages?
|
||||
message_finder.has_messages?
|
||||
validator.has_messages?
|
||||
end
|
||||
|
||||
def errors_for_attribute_match?
|
||||
|
@ -265,7 +286,7 @@ module Shoulda
|
|||
end
|
||||
|
||||
def errors_for_attribute
|
||||
message_finder.messages
|
||||
validator.formatted_messages
|
||||
end
|
||||
|
||||
def errors_match_regexp?
|
||||
|
@ -280,30 +301,25 @@ module Shoulda
|
|||
end
|
||||
end
|
||||
|
||||
def any_range_error_occurred?
|
||||
validator.captured_range_error?
|
||||
end
|
||||
|
||||
def expectation
|
||||
parts = [
|
||||
error_source,
|
||||
includes_expected_message,
|
||||
expected_messages_description,
|
||||
"when #{attribute_to_set} is set to #{value.inspect}"
|
||||
]
|
||||
|
||||
parts.join(' ').squeeze(' ')
|
||||
end
|
||||
|
||||
def includes_expected_message
|
||||
if expected_message
|
||||
"to include #{expected_message.inspect}"
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
def error_source
|
||||
message_finder.source_description
|
||||
def expected_messages_description
|
||||
validator.expected_messages_description(expected_message)
|
||||
end
|
||||
|
||||
def error_description
|
||||
message_finder.messages_description
|
||||
validator.messages_description
|
||||
end
|
||||
|
||||
def allowed_values
|
||||
|
@ -325,7 +341,7 @@ module Shoulda
|
|||
end
|
||||
|
||||
def default_expected_message
|
||||
message_finder.expected_message_from(default_attribute_message)
|
||||
validator.expected_message_from(default_attribute_message)
|
||||
end
|
||||
|
||||
def default_attribute_message
|
||||
|
@ -348,10 +364,6 @@ module Shoulda
|
|||
def model_name
|
||||
instance.class.to_s.underscore
|
||||
end
|
||||
|
||||
def message_finder
|
||||
message_finder_factory.new(instance, attribute_to_check_message_against, context)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
# @private
|
||||
class ExceptionMessageFinder
|
||||
def initialize(instance, attribute, context=nil)
|
||||
@instance = instance
|
||||
@attribute = attribute
|
||||
@context = context
|
||||
end
|
||||
|
||||
def allow_description(allowed_values)
|
||||
"doesn't raise when #{@attribute} is set to #{allowed_values}"
|
||||
end
|
||||
|
||||
def messages_description
|
||||
if has_messages?
|
||||
": #{messages.join.inspect}"
|
||||
else
|
||||
' no exception'
|
||||
end
|
||||
end
|
||||
|
||||
def has_messages?
|
||||
messages.any?
|
||||
end
|
||||
|
||||
def messages
|
||||
@messages ||= validate_and_rescue
|
||||
end
|
||||
|
||||
def source_description
|
||||
'exception'
|
||||
end
|
||||
|
||||
def expected_message_from(attribute_message)
|
||||
"#{human_attribute_name} #{attribute_message}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_and_rescue
|
||||
@instance.valid?(@context)
|
||||
[]
|
||||
rescue ::ActiveModel::StrictValidationFailed => exception
|
||||
[exception.message]
|
||||
end
|
||||
|
||||
def human_attribute_name
|
||||
@instance.class.human_attribute_name(@attribute)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -24,32 +24,18 @@ module Shoulda
|
|||
end.join("\n")
|
||||
end
|
||||
|
||||
# Helper method that determines the default error message used by Active
|
||||
# Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
|
||||
# introduced I18n module used for localization. Use with Rails 3.0 and
|
||||
# up will delegate to ActiveModel::Errors.generate_error if a model
|
||||
# instance is given.
|
||||
#
|
||||
# default_error_message(:blank)
|
||||
# default_error_message(:too_short, count: 5)
|
||||
# default_error_message(:too_long, count: 60)
|
||||
# default_error_message(:blank, model_name: 'user', attribute: 'name')
|
||||
# default_error_message(:blank, instance: #<Model>, attribute: 'name')
|
||||
def default_error_message(key, options = {})
|
||||
def default_error_message(type, options = {})
|
||||
model_name = options.delete(:model_name)
|
||||
attribute = options.delete(:attribute)
|
||||
instance = options.delete(:instance)
|
||||
|
||||
if instance && instance.errors.respond_to?(:generate_message)
|
||||
instance.errors.generate_message(attribute.to_sym, key, options)
|
||||
else
|
||||
default_translation = [ :"activerecord.errors.models.#{model_name}.#{key}",
|
||||
:"activerecord.errors.messages.#{key}",
|
||||
:"errors.attributes.#{attribute}.#{key}",
|
||||
:"errors.messages.#{key}" ]
|
||||
I18n.translate(:"activerecord.errors.models.#{model_name}.attributes.#{attribute}.#{key}",
|
||||
{ default: default_translation }.merge(options))
|
||||
end
|
||||
RailsShim.generate_validation_message(
|
||||
instance,
|
||||
attribute.to_sym,
|
||||
type,
|
||||
model_name,
|
||||
options
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -100,16 +100,16 @@ module Shoulda
|
|||
|
||||
def assertions
|
||||
case @operator
|
||||
when :>
|
||||
[false, false, true]
|
||||
when :>=
|
||||
[false, true, true]
|
||||
when :==
|
||||
[false, true, false]
|
||||
when :<
|
||||
[true, false, false]
|
||||
when :<=
|
||||
[true, true, false]
|
||||
when :>
|
||||
[false, false, true]
|
||||
when :>=
|
||||
[false, true, true]
|
||||
when :==
|
||||
[false, true, false]
|
||||
when :<
|
||||
[true, false, false]
|
||||
when :<=
|
||||
[true, true, false]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
module StrictValidator
|
||||
def allow_description(allowed_values)
|
||||
"doesn't raise when #{attribute} is set to #{allowed_values}"
|
||||
end
|
||||
|
||||
def expected_message_from(attribute_message)
|
||||
"#{human_attribute_name} #{attribute_message}"
|
||||
end
|
||||
|
||||
def formatted_messages
|
||||
[messages.first.message]
|
||||
end
|
||||
|
||||
def messages_description
|
||||
if has_messages?
|
||||
': ' + messages.first.message.inspect
|
||||
else
|
||||
' no exception'
|
||||
end
|
||||
end
|
||||
|
||||
def expected_messages_description(expected_message)
|
||||
if expected_message
|
||||
"exception to include #{expected_message.inspect}"
|
||||
else
|
||||
'an exception to have been raised'
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def collect_messages
|
||||
validation_exceptions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validation_exceptions
|
||||
record.valid?(context)
|
||||
[]
|
||||
rescue ::ActiveModel::StrictValidationFailed => exception
|
||||
[exception]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,69 +0,0 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
# @private
|
||||
class ValidationMessageFinder
|
||||
include Helpers
|
||||
|
||||
def initialize(instance, attribute, context=nil)
|
||||
@instance = instance
|
||||
@attribute = attribute
|
||||
@context = context
|
||||
end
|
||||
|
||||
def allow_description(allowed_values)
|
||||
"allow #{@attribute} to be set to #{allowed_values}"
|
||||
end
|
||||
|
||||
def expected_message_from(attribute_message)
|
||||
attribute_message
|
||||
end
|
||||
|
||||
def has_messages?
|
||||
errors.present?
|
||||
end
|
||||
|
||||
def source_description
|
||||
'errors'
|
||||
end
|
||||
|
||||
def messages_description
|
||||
if errors.empty?
|
||||
' no errors'
|
||||
else
|
||||
" errors:\n#{pretty_error_messages(validated_instance)}"
|
||||
end
|
||||
end
|
||||
|
||||
def messages
|
||||
Array(messages_for_attribute)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def messages_for_attribute
|
||||
if errors.respond_to?(:[])
|
||||
errors[@attribute]
|
||||
else
|
||||
errors.on(@attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def errors
|
||||
validated_instance.errors
|
||||
end
|
||||
|
||||
def validated_instance
|
||||
@validated_instance ||= validate_instance
|
||||
end
|
||||
|
||||
def validate_instance
|
||||
@instance.valid?(*@context)
|
||||
@instance
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
# @private
|
||||
class Validator
|
||||
include Helpers
|
||||
|
||||
attr_writer :attribute, :context, :record
|
||||
|
||||
def initialize
|
||||
reset
|
||||
end
|
||||
|
||||
def reset
|
||||
@messages = nil
|
||||
end
|
||||
|
||||
def strict=(strict)
|
||||
@strict = strict
|
||||
|
||||
if strict
|
||||
extend StrictValidator
|
||||
end
|
||||
end
|
||||
|
||||
def capture_range_error(exception)
|
||||
@captured_range_error = exception
|
||||
extend ValidatorWithCapturedRangeError
|
||||
end
|
||||
|
||||
def allow_description(allowed_values)
|
||||
"allow #{attribute} to be set to #{allowed_values}"
|
||||
end
|
||||
|
||||
def expected_message_from(attribute_message)
|
||||
attribute_message
|
||||
end
|
||||
|
||||
def messages
|
||||
@messages ||= collect_messages
|
||||
end
|
||||
|
||||
def formatted_messages
|
||||
messages
|
||||
end
|
||||
|
||||
def has_messages?
|
||||
messages.any?
|
||||
end
|
||||
|
||||
def messages_description
|
||||
if has_messages?
|
||||
" errors:\n#{pretty_error_messages(record)}"
|
||||
else
|
||||
' no errors'
|
||||
end
|
||||
end
|
||||
|
||||
def expected_messages_description(expected_message)
|
||||
if expected_message
|
||||
"errors to include #{expected_message.inspect}"
|
||||
else
|
||||
'errors'
|
||||
end
|
||||
end
|
||||
|
||||
def captured_range_error?
|
||||
!!captured_range_error
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :attribute, :context, :strict, :record,
|
||||
:captured_range_error
|
||||
|
||||
def collect_messages
|
||||
validation_errors
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def strict?
|
||||
!!@strict
|
||||
end
|
||||
|
||||
def collect_errors_or_exceptions
|
||||
collect_messages
|
||||
rescue RangeError => exception
|
||||
capture_range_error(exception)
|
||||
[]
|
||||
end
|
||||
|
||||
def validation_errors
|
||||
if context
|
||||
record.valid?(context)
|
||||
else
|
||||
record.valid?
|
||||
end
|
||||
|
||||
if record.errors.respond_to?(:[])
|
||||
record.errors[attribute]
|
||||
else
|
||||
record.errors.on(attribute)
|
||||
end
|
||||
end
|
||||
|
||||
def human_attribute_name
|
||||
record.class.human_attribute_name(attribute)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
module Shoulda
|
||||
module Matchers
|
||||
module ActiveModel
|
||||
module ValidatorWithCapturedRangeError
|
||||
def messages_description
|
||||
' RangeError: ' + captured_range_error.message.inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -65,6 +65,28 @@ module Shoulda
|
|||
end
|
||||
end
|
||||
|
||||
def self.generate_validation_message(record, attribute, type, model_name, options)
|
||||
if record && record.errors.respond_to?(:generate_message)
|
||||
record.errors.generate_message(attribute.to_sym, type, options)
|
||||
else
|
||||
simply_generate_validation_message(attribute, type, model_name, options)
|
||||
end
|
||||
rescue RangeError
|
||||
simply_generate_validation_message(attribute, type, model_name, options)
|
||||
end
|
||||
|
||||
def self.simply_generate_validation_message(attribute, type, model_name, options)
|
||||
default_translation_keys = [
|
||||
:"activerecord.errors.models.#{model_name}.#{type}",
|
||||
:"activerecord.errors.messages.#{type}",
|
||||
:"errors.attributes.#{attribute}.#{type}",
|
||||
:"errors.messages.#{type}"
|
||||
]
|
||||
primary_translation_key = :"activerecord.errors.models.#{model_name}.attributes.#{attribute}.#{type}"
|
||||
translate_options = { default: default_translation_keys }.merge(options)
|
||||
I18n.translate(primary_translation_key, translate_options)
|
||||
end
|
||||
|
||||
def self.active_record_major_version
|
||||
::ActiveRecord::VERSION::MAJOR
|
||||
end
|
||||
|
|
|
@ -7,8 +7,10 @@ module UnitTests
|
|||
def custom_validation(options = {}, &block)
|
||||
attribute_name = options.fetch(:attribute_name, :attr)
|
||||
attribute_type = options.fetch(:attribute_type, :integer)
|
||||
column_options = options.fetch(:column_options, {})
|
||||
attribute_options = { type: attribute_type, options: column_options }
|
||||
|
||||
define_model(:example, attribute_name => attribute_type) do
|
||||
define_model(:example, attribute_name => attribute_options) do
|
||||
validate :custom_validation
|
||||
|
||||
define_method(:custom_validation, &block)
|
||||
|
|
|
@ -5,6 +5,10 @@ module UnitTests
|
|||
example_group.extend(self)
|
||||
end
|
||||
|
||||
def active_model_version
|
||||
Tests::Version.new(::ActiveModel::VERSION::STRING)
|
||||
end
|
||||
|
||||
def active_model_3_1?
|
||||
(::ActiveModel::VERSION::MAJOR == 3 && ::ActiveModel::VERSION::MINOR >= 1) || active_model_4_0?
|
||||
end
|
||||
|
@ -16,5 +20,9 @@ module UnitTests
|
|||
def active_model_4_0?
|
||||
::ActiveModel::VERSION::MAJOR == 4
|
||||
end
|
||||
|
||||
def active_model_supports_strict?
|
||||
active_model_version >= 3.2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
module UnitTests
|
||||
module ActiveRecordVersions
|
||||
def self.configure_example_group(example_group)
|
||||
example_group.include(self)
|
||||
example_group.extend(self)
|
||||
end
|
||||
|
||||
def active_record_version
|
||||
Tests::Version.new(ActiveRecord::VERSION::STRING)
|
||||
end
|
||||
|
||||
def active_record_can_raise_range_error?
|
||||
active_record_version >= 4.2
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,19 +6,19 @@ module UnitTests
|
|||
end
|
||||
|
||||
def rails_version
|
||||
Gem::Version.new(Rails::VERSION::STRING)
|
||||
Tests::Version.new(Rails::VERSION::STRING)
|
||||
end
|
||||
|
||||
def rails_3_x?
|
||||
Gem::Requirement.new('~> 3.0').satisfied_by?(rails_version)
|
||||
rails_version =~ '~> 3.0'
|
||||
end
|
||||
|
||||
def rails_4_x?
|
||||
Gem::Requirement.new('~> 4.0').satisfied_by?(rails_version)
|
||||
rails_version =~ '~> 4.0'
|
||||
end
|
||||
|
||||
def rails_gte_4_1?
|
||||
Gem::Requirement.new('>= 4.1').satisfied_by?(rails_version)
|
||||
rails_version >= 4.1
|
||||
end
|
||||
|
||||
def active_record_supports_enum?
|
||||
|
|
|
@ -227,4 +227,78 @@ describe Shoulda::Matchers::ActiveModel::AllowValueMatcher, type: :model do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
if active_record_can_raise_range_error?
|
||||
context 'when the value is outside of the range of the column' do
|
||||
context 'not qualified with strict' do
|
||||
it 'rejects, failing with the correct message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> { expect(record).to allow_value(100000).for(:attr) }
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect errors when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
|
||||
context 'qualified with a message' do
|
||||
it 'ignores any specified message, failing with the correct message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> do
|
||||
expect(record).
|
||||
to allow_value(100000).
|
||||
for(:attr).
|
||||
with_message('some message')
|
||||
end
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect errors to include "some message" when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if active_model_supports_strict?
|
||||
context 'qualified with strict' do
|
||||
it 'rejects, failing with the correct message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> do
|
||||
expect(record).
|
||||
to allow_value(100000).
|
||||
for(:attr).
|
||||
strict
|
||||
end
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect an exception to have been raised when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
|
||||
context 'qualified with a message' do
|
||||
it 'ignores any specified message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> do
|
||||
expect(record).
|
||||
to allow_value(100000).
|
||||
for(:attr).
|
||||
with_message('some message').
|
||||
strict
|
||||
end
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect exception to include "some message" when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -79,7 +79,82 @@ describe Shoulda::Matchers::ActiveModel::DisallowValueMatcher, type: :model do
|
|||
end
|
||||
end
|
||||
|
||||
if active_record_can_raise_range_error?
|
||||
context 'when the value is outside of the range of the column' do
|
||||
context 'not qualified with strict' do
|
||||
it 'accepts, failing with the correct message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> { expect(record).not_to disallow_value(100000).for(:attr) }
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect errors when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
|
||||
context 'qualified with a message' do
|
||||
it 'ignores any specified message, failing with the correct message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> do
|
||||
expect(record).
|
||||
not_to disallow_value(100000).
|
||||
for(:attr).
|
||||
with_message('some message')
|
||||
end
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect errors to include "some message" when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if active_model_supports_strict?
|
||||
context 'qualified with strict' do
|
||||
it 'accepts, failing with the correct message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> do
|
||||
expect(record).
|
||||
not_to disallow_value(100000).
|
||||
for(:attr).
|
||||
strict
|
||||
end
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect an exception to have been raised when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
|
||||
context 'qualified with a message' do
|
||||
it 'ignores any specified message' do
|
||||
attribute_options = { type: :integer, options: { limit: 2 } }
|
||||
record = define_model(:example, attr: attribute_options).new
|
||||
assertion = -> do
|
||||
expect(record).
|
||||
not_to disallow_value(100000).
|
||||
for(:attr).
|
||||
with_message('some message').
|
||||
strict
|
||||
end
|
||||
message = <<-MESSAGE.strip_heredoc.strip
|
||||
Did not expect exception to include "some message" when attr is set to 100000,
|
||||
got RangeError: "100000 is out of range for ActiveRecord::Type::Integer with limit 2"
|
||||
MESSAGE
|
||||
expect(&assertion).to fail_with_message(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def matcher(value)
|
||||
described_class.new(value)
|
||||
end
|
||||
alias_method :disallow_value, :matcher
|
||||
end
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
require 'unit_spec_helper'
|
||||
|
||||
describe Shoulda::Matchers::ActiveModel::ExceptionMessageFinder, type: :model do
|
||||
if active_model_3_2?
|
||||
context '#allow_description' do
|
||||
it 'describes its attribute' do
|
||||
finder = build_finder(attribute: :attr)
|
||||
|
||||
description = finder.allow_description('allowed values')
|
||||
|
||||
expect(description).to eq %q(doesn't raise when attr is set to allowed values)
|
||||
end
|
||||
end
|
||||
|
||||
context '#expected_message_from' do
|
||||
it 'returns the message with the attribute name prefixed' do
|
||||
finder = build_finder(attribute: :attr)
|
||||
|
||||
message = finder.expected_message_from('some message')
|
||||
|
||||
expect(message).to eq 'Attr some message'
|
||||
end
|
||||
end
|
||||
|
||||
context '#has_messages?' do
|
||||
it 'has messages when some validations fail' do
|
||||
finder = build_finder(format: /abc/, value: 'xyz')
|
||||
|
||||
result = finder.has_messages?
|
||||
|
||||
expect(result).to eq true
|
||||
end
|
||||
|
||||
it 'has no messages when all validations pass' do
|
||||
finder = build_finder(format: /abc/, value: 'abc')
|
||||
|
||||
result = finder.has_messages?
|
||||
|
||||
expect(result).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context '#messages' do
|
||||
it 'returns errors for the given attribute' do
|
||||
finder = build_finder(
|
||||
attribute: :attr,
|
||||
format: /abc/,
|
||||
value: 'xyz'
|
||||
)
|
||||
|
||||
messages = finder.messages
|
||||
|
||||
expect(messages).to eq ['Attr is invalid']
|
||||
end
|
||||
end
|
||||
|
||||
context '#messages_description' do
|
||||
it 'describes errors for the given attribute' do
|
||||
finder = build_finder(
|
||||
attribute: :attr,
|
||||
format: /abc/,
|
||||
value: 'xyz'
|
||||
)
|
||||
|
||||
description = finder.messages_description
|
||||
|
||||
expect(description).to eq ': "Attr is invalid"'
|
||||
end
|
||||
|
||||
it 'describes errors when there are none' do
|
||||
finder = build_finder(format: /abc/, value: 'abc')
|
||||
|
||||
description = finder.messages_description
|
||||
|
||||
expect(description).to eq ' no exception'
|
||||
end
|
||||
end
|
||||
|
||||
context '#source_description' do
|
||||
it 'describes the source of its messages' do
|
||||
finder = build_finder
|
||||
|
||||
description = finder.source_description
|
||||
|
||||
expect(description).to eq 'exception'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def build_finder(arguments = {})
|
||||
arguments[:attribute] ||= :attr
|
||||
instance = build_instance_validating(
|
||||
arguments[:attribute],
|
||||
arguments[:format] || /abc/,
|
||||
arguments[:value] || 'abc'
|
||||
)
|
||||
Shoulda::Matchers::ActiveModel::ExceptionMessageFinder.new(
|
||||
instance,
|
||||
arguments[:attribute]
|
||||
)
|
||||
end
|
||||
|
||||
def build_instance_validating(attribute, format, value)
|
||||
model_class = define_model(:example, attribute => :string) do
|
||||
attr_accessible attribute
|
||||
validates_format_of attribute, with: format, strict: true
|
||||
end
|
||||
|
||||
model_class.new(attribute => value)
|
||||
end
|
||||
end
|
|
@ -68,6 +68,30 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
|
|||
end
|
||||
end
|
||||
|
||||
context 'against an attribute with a specific column limit' do
|
||||
it 'does not raise an exception when attempting to use the matcher' do
|
||||
possible_values = (1..5).to_a
|
||||
builder = build_object_allowing(possible_values)
|
||||
assertion = -> { expect_to_match_on_values(builder, possible_values) }
|
||||
expect(&assertion).not_to raise_error
|
||||
end
|
||||
|
||||
def build_object(options = {}, &block)
|
||||
build_object_with_generic_attribute(
|
||||
options.merge(
|
||||
column_type: :integer,
|
||||
column_options: { limit: 2 },
|
||||
value: 1
|
||||
),
|
||||
&block
|
||||
)
|
||||
end
|
||||
|
||||
def expect_to_match_on_values(builder, values, &block)
|
||||
expect_to_match_in_array(builder, values, &block)
|
||||
end
|
||||
end
|
||||
|
||||
context "against a float attribute" do
|
||||
it_behaves_like 'it supports in_array',
|
||||
possible_values: [1.0, 2.0, 3.0, 4.0, 5.0],
|
||||
|
|
|
@ -123,6 +123,72 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
|
|||
end
|
||||
end
|
||||
|
||||
context 'qualified with less_than_or_equal_to' do
|
||||
it 'does not raise an error if the given value is right at the allowed max value for the column' do
|
||||
record = record_with_integer_column_of_limit(:attr, 2, less_than_or_equal_to: 32767)
|
||||
assertion = -> {
|
||||
expect(record).to validate_numericality_of(:attr).is_less_than_or_equal_to(32767)
|
||||
}
|
||||
expect(&assertion).not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'qualified with less_than' do
|
||||
it 'does not raise an error if the given value is right at the allowed max value for the column' do
|
||||
record = record_with_integer_column_of_limit(:attr, 2, less_than: 32767)
|
||||
assertion = -> {
|
||||
expect(record).to validate_numericality_of(:attr).is_less_than(32767)
|
||||
}
|
||||
expect(&assertion).not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'qualified with equal_to' do
|
||||
it 'does not raise an error if the given value is right at the allowed min value for the column' do
|
||||
record = record_with_integer_column_of_limit(:attr, 2, equal_to: -32768)
|
||||
assertion = -> {
|
||||
expect(record).to validate_numericality_of(:attr).is_equal_to(-32768)
|
||||
}
|
||||
expect(&assertion).not_to raise_error
|
||||
end
|
||||
|
||||
it 'does not raise an error if the given value is right at the allowed max value for the column' do
|
||||
record = record_with_integer_column_of_limit(:attr, 2, equal_to: 32767)
|
||||
assertion = -> {
|
||||
expect(record).to validate_numericality_of(:attr).is_equal_to(32767)
|
||||
}
|
||||
expect(&assertion).not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'qualified with greater_than_or_equal to' do
|
||||
it 'does not raise an error if the given value is right at the allowed min value for the column' do
|
||||
record = record_with_integer_column_of_limit(:attr, 2,
|
||||
greater_than_or_equal_to: -32768
|
||||
)
|
||||
assertion = -> {
|
||||
expect(record).
|
||||
to validate_numericality_of(:attr).
|
||||
is_greater_than_or_equal_to(-32768)
|
||||
}
|
||||
expect(&assertion).not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'qualified with greater_than' do
|
||||
it 'does not raise an error if the given value is right at the allowed min value for the column' do
|
||||
record = record_with_integer_column_of_limit(:attr, 2,
|
||||
greater_than: -32768
|
||||
)
|
||||
assertion = -> {
|
||||
expect(record).
|
||||
to validate_numericality_of(:attr).
|
||||
is_greater_than(-32768)
|
||||
}
|
||||
expect(&assertion).not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple options together' do
|
||||
context 'the success cases' do
|
||||
it do
|
||||
|
@ -349,4 +415,11 @@ describe Shoulda::Matchers::ActiveModel::ValidateNumericalityOfMatcher, type: :m
|
|||
def matcher
|
||||
validate_numericality_of(:attr)
|
||||
end
|
||||
|
||||
def record_with_integer_column_of_limit(attribute, limit, validation_options = {})
|
||||
column_options = { type: :integer, options: { limit: limit } }
|
||||
define_model :example, attribute => column_options do
|
||||
validates_numericality_of attribute, validation_options
|
||||
end.new
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
require 'unit_spec_helper'
|
||||
|
||||
describe Shoulda::Matchers::ActiveModel::ValidationMessageFinder do
|
||||
context '#allow_description' do
|
||||
it 'describes its attribute' do
|
||||
finder = build_finder(attribute: :attr)
|
||||
|
||||
description = finder.allow_description('allowed values')
|
||||
|
||||
expect(description).to eq 'allow attr to be set to allowed values'
|
||||
end
|
||||
end
|
||||
|
||||
context '#expected_message_from' do
|
||||
it 'returns the message as-is' do
|
||||
finder = build_finder
|
||||
|
||||
message = finder.expected_message_from('some message')
|
||||
|
||||
expect(message).to eq 'some message'
|
||||
end
|
||||
end
|
||||
|
||||
context '#has_messages?' do
|
||||
it 'has messages when some validations fail' do
|
||||
finder = build_finder(format: /abc/, value: 'xyz')
|
||||
|
||||
result = finder.has_messages?
|
||||
|
||||
expect(result).to eq true
|
||||
end
|
||||
|
||||
it 'has no messages when all validations pass' do
|
||||
finder = build_finder(format: /abc/, value: 'abc')
|
||||
|
||||
result = finder.has_messages?
|
||||
|
||||
expect(result).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context '#messages' do
|
||||
it 'returns errors for the given attribute' do
|
||||
finder = build_finder(format: /abc/, value: 'xyz')
|
||||
|
||||
messages = finder.messages
|
||||
|
||||
expect(messages).to eq ['is invalid']
|
||||
end
|
||||
|
||||
it 'returns an empty array if there are no errors for the given attribute' do
|
||||
finder = build_finder
|
||||
|
||||
messages = finder.messages
|
||||
|
||||
expect(messages).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context '#messages_description' do
|
||||
it 'describes errors for the given attribute' do
|
||||
finder = build_finder(
|
||||
attribute: :attr,
|
||||
format: /abc/,
|
||||
value: 'xyz'
|
||||
)
|
||||
|
||||
description = finder.messages_description
|
||||
|
||||
expect(description).to eq(
|
||||
%{ errors:\n* "is invalid" (attribute: attr, value: "xyz")}
|
||||
)
|
||||
end
|
||||
|
||||
it 'describes errors when there are none' do
|
||||
finder = build_finder(format: /abc/, value: 'abc')
|
||||
|
||||
description = finder.messages_description
|
||||
|
||||
expect(description).to eq ' no errors'
|
||||
end
|
||||
|
||||
it 'should not fetch attribute values for errors that were copied from an autosaved belongs_to association' do
|
||||
instance = define_model(:example) do
|
||||
validate do |record|
|
||||
record.errors.add('association.association_attribute', 'is invalid')
|
||||
end
|
||||
end.new
|
||||
finder = Shoulda::Matchers::ActiveModel::ValidationMessageFinder.new(instance, :attribute)
|
||||
|
||||
expect(finder.messages_description).to eq(
|
||||
%{ errors:\n* "is invalid" (attribute: association.association_attribute)}
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context '#source_description' do
|
||||
it 'describes the source of its messages' do
|
||||
finder = build_finder
|
||||
|
||||
description = finder.source_description
|
||||
|
||||
expect(description).to eq 'errors'
|
||||
end
|
||||
end
|
||||
|
||||
def build_finder(arguments = {})
|
||||
arguments[:attribute] ||= :attr
|
||||
instance = build_instance_validating(
|
||||
arguments[:attribute],
|
||||
arguments[:format] || /abc/,
|
||||
arguments[:value] || 'abc'
|
||||
)
|
||||
Shoulda::Matchers::ActiveModel::ValidationMessageFinder.new(
|
||||
instance,
|
||||
arguments[:attribute]
|
||||
)
|
||||
end
|
||||
|
||||
def build_instance_validating(attribute, format, value)
|
||||
model_class = define_model(:example, attribute => :string) do
|
||||
attr_accessible attribute
|
||||
validates_format_of attribute, with: format
|
||||
end
|
||||
|
||||
model_class.new(attribute => value)
|
||||
end
|
||||
end
|
|
@ -58,6 +58,8 @@ RSpec.configure do |config|
|
|||
UnitTests::MailerBuilder.configure_example_group(config)
|
||||
UnitTests::ModelBuilder.configure_example_group(config)
|
||||
UnitTests::RailsVersions.configure_example_group(config)
|
||||
UnitTests::ActiveRecordVersions.configure_example_group(config)
|
||||
UnitTests::ActiveModelVersions.configure_example_group(config)
|
||||
|
||||
config.include UnitTests::Matchers
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue