thoughtbot--shoulda-matchers/lib/shoulda/matchers/active_model/disallow_value_matcher.rb

79 lines
1.7 KiB
Ruby
Raw Normal View History

require 'forwardable'
module Shoulda
module Matchers
module ActiveModel
# @private
class DisallowValueMatcher
extend Forwardable
def_delegators(
:allow_matcher,
:_after_setting_value,
:attribute_changed_value_message=,
:attribute_to_set,
:description,
:expects_strict?,
:failure_message_preface,
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
:failure_message_preface=,
:ignore_interference_by_writer,
:last_attribute_setter_used,
:last_value_set,
:model,
:simple_description,
:values_to_preset=,
)
def initialize(value)
@allow_matcher = AllowValueMatcher.new(value)
end
def matches?(subject)
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
allow_matcher.does_not_match?(subject)
end
def does_not_match?(subject)
allow_matcher.matches?(subject)
end
def for(attribute)
allow_matcher.for(attribute)
self
end
def on(context)
allow_matcher.on(context)
self
end
def with_message(message, options={})
allow_matcher.with_message(message, options)
self
end
def strict(strict = true)
allow_matcher.strict(strict)
self
end
def ignoring_interference_by_writer(value = :always)
allow_matcher.ignoring_interference_by_writer(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
self
end
def failure_message
allow_matcher.failure_message_when_negated
end
def failure_message_when_negated
allow_matcher.failure_message
end
protected
attr_reader :allow_matcher
end
end
end
end