thoughtbot--shoulda-matchers/lib/shoulda/matchers/active_model/validate_exclusion_of_match...

250 lines
6.9 KiB
Ruby
Raw Normal View History

module Shoulda
2011-10-16 17:49:58 +00:00
module Matchers
module ActiveModel
# The `validate_exclusion_of` matcher tests usage of the
# `validates_exclusion_of` validation, asserting that an attribute cannot
# take a blacklist of values, and inversely, can take values outside of
# this list.
#
# If your blacklist is an array of values, use `in_array`:
#
# class Game
# include ActiveModel::Model
# attr_accessor :supported_os
#
# validates_exclusion_of :supported_os, in: ['Mac', 'Linux']
# end
#
# # RSpec
# RSpec.describe Game, type: :model do
# it do
# should validate_exclusion_of(:supported_os).
# in_array(['Mac', 'Linux'])
# end
# end
#
# # Minitest (Shoulda)
# class GameTest < ActiveSupport::TestCase
# should validate_exclusion_of(:supported_os).
# in_array(['Mac', 'Linux'])
# end
#
2014-11-06 09:19:12 +00:00
# If your blacklist is a range of values, use `in_range`:
#
# class Game
# include ActiveModel::Model
# attr_accessor :floors_with_enemies
#
# validates_exclusion_of :floors_with_enemies, in: 5..8
# end
#
# # RSpec
# RSpec.describe Game, type: :model do
# it do
# should validate_exclusion_of(:floors_with_enemies).
# in_range(5..8)
# end
# end
#
# # Minitest (Shoulda)
# class GameTest < ActiveSupport::TestCase
# should validate_exclusion_of(:floors_with_enemies).
# in_range(5..8)
# end
#
# #### Qualifiers
#
# ##### on
#
# Use `on` if your validation applies only under a certain context.
#
# class Game
# include ActiveModel::Model
# attr_accessor :weapon
#
# validates_exclusion_of :weapon,
# in: ['pistol', 'paintball gun', 'stick'],
# on: :create
# end
#
# # RSpec
# RSpec.describe Game, type: :model do
# it do
# should validate_exclusion_of(:weapon).
# in_array(['pistol', 'paintball gun', 'stick']).
# on(:create)
# end
# end
#
# # Minitest (Shoulda)
# class GameTest < ActiveSupport::TestCase
# should validate_exclusion_of(:weapon).
# in_array(['pistol', 'paintball gun', 'stick']).
# on(:create)
# end
#
# ##### with_message
#
# Use `with_message` if you are using a custom validation message.
#
# class Game
# include ActiveModel::Model
# attr_accessor :weapon
#
# validates_exclusion_of :weapon,
# in: ['pistol', 'paintball gun', 'stick'],
# message: 'You chose a puny weapon'
# end
#
# # RSpec
# RSpec.describe Game, type: :model do
# it do
# should validate_exclusion_of(:weapon).
# in_array(['pistol', 'paintball gun', 'stick']).
# with_message('You chose a puny weapon')
# end
# end
2011-10-16 17:49:58 +00:00
#
# # Minitest (Shoulda)
# class GameTest < ActiveSupport::TestCase
# should validate_exclusion_of(:weapon).
# in_array(['pistol', 'paintball gun', 'stick']).
# with_message('You chose a puny weapon')
# end
2011-10-16 17:49:58 +00:00
#
# @return [ValidateExclusionOfMatcher]
2011-10-16 17:49:58 +00:00
#
def validate_exclusion_of(attr)
ValidateExclusionOfMatcher.new(attr)
2011-10-16 17:49:58 +00:00
end
2014-07-23 04:52:16 +00:00
# @private
class ValidateExclusionOfMatcher < ValidationMatcher
def initialize(attribute)
super(attribute)
@expected_message = :exclusion
@array = nil
@range = nil
end
2012-08-19 09:07:05 +00:00
def in_array(array)
@array = array
self
end
2011-10-16 17:49:58 +00:00
def in_range(range)
@range = range
@minimum = range.first
@maximum = range.max
2011-10-16 17:49:58 +00:00
self
end
def simple_description
if @range
"validate that :#{@attribute} lies outside the range " +
Shoulda::Matchers::Util.inspect_range(@range)
else
description = "validate that :#{@attribute}"
2011-10-16 17:49:58 +00:00
description <<
if @array.many?
" is neither #{inspected_array}"
else
" is not #{inspected_array}"
end
description
end
2011-10-16 17:49:58 +00:00
end
def matches?(subject)
super(subject)
2012-08-19 09:07:05 +00:00
if @range
allows_lower_value &&
disallows_minimum_value &&
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
disallows_maximum_value &&
allows_higher_value
2012-08-19 09:07:05 +00:00
elsif @array
disallows_all_values_in_array?
end
2011-10-16 17:49:58 +00:00
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
def does_not_match?(subject)
super(subject)
if @range
disallows_lower_value ||
allows_minimum_value ||
allows_maximum_value ||
disallows_higher_value
elsif @array
allows_any_values_in_array?
end
end
2011-10-16 17:49:58 +00:00
private
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
def allows_any_values_in_array?
@array.any? do |value|
allows_value_of(value, @expected_message)
end
end
2012-08-19 09:07:05 +00:00
def disallows_all_values_in_array?
@array.all? do |value|
disallows_value_of(value, @expected_message)
2012-08-19 09:07:05 +00:00
end
end
2011-10-16 17:49:58 +00:00
def allows_lower_value
@minimum == 0 || allows_value_of(@minimum - 1, @expected_message)
2011-10-16 17:49:58 +00:00
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
def disallows_lower_value
@minimum != 0 && disallows_value_of(@minimum - 1, @expected_message)
end
def allows_minimum_value
allows_value_of(@minimum, @expected_message)
2011-10-16 17:49:58 +00:00
end
def disallows_minimum_value
disallows_value_of(@minimum, @expected_message)
2011-10-16 17:49:58 +00:00
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
def allows_maximum_value
allows_value_of(@maximum, @expected_message)
end
2011-10-16 17:49:58 +00:00
def disallows_maximum_value
disallows_value_of(@maximum, @expected_message)
2011-10-16 17:49:58 +00:00
end
Fix negative versions of validation matchers When using a validation matcher in the negative, i.e.: should_not validate_*(...) as opposed to: should validate_*(...) ...it's common to receive the following error: undefined method `attribute_setter' for nil:NilClass This happens particularly when using a matcher that makes use of AllowValueMatcher or DisallowValueMatcher internally (which all of the validation matchers do). Whenever you make an assertion by using a matcher in the negative as opposed to the positive, RSpec still calls the `matches?` method for that matcher; however, the assertion will pass if that returns *false* as opposed to true. In other words, it just inverts the result. However, whenever we are using AllowValueMatcher or DisallowValueMatcher, it doesn't really work to invert the result. like this. This is because AllowValueMatcher and DisallowValueMatcher, despite their name, aren't truly opposites of each other. AllowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations fail, store the value that caused the failure along with the validation errors and return false 5. Otherwise, return true However, DisallowValueMatcher performs these steps: 1. Set the attribute on the record to some value 2. Run validations on the record 3. Ask whether validations pass or fail 4. If validations *pass*, store the value that caused the failure along with some metadata and return false 5. Otherwise, return true This difference in logic is achieved by having AllowValueMatcher implement `does_not_match?` and then having DisallowValueMatcher use this for its positive case and use `matches?` for its negative case. It's easy to see because of this that `does_not_match?` is not the same as `!matches?` and vice versa. So a matcher that makes use of these submatchers internally needs to use their opposite versions whenever that matcher is used in the negative case. In other words, all of the matchers need a `does_not_match?` which is like `matches?`, except that all of the logic is inverted, and in all the cases in which AllowValueMatcher is used, DisallowValueMatcher needs to be used. Doing this ensures that when `failure_message` is called on AllowValueMatcher or DisallowValueMatcher, step 4 in the list of steps above stores a proper value that can then be referenced in the failure message for the validation matcher itself.
2018-09-08 20:59:24 +00:00
def allows_higher_value
allows_value_of(@maximum + 1, @expected_message)
end
def disallows_higher_value
disallows_value_of(@maximum + 1, @expected_message)
end
2012-08-19 09:07:05 +00:00
def inspect_message
if @range
@range.inspect
else
@array.inspect
end
end
def inspected_array
Shoulda::Matchers::Util.inspect_values(@array).to_sentence(
two_words_connector: ' nor ',
last_word_connector: ', nor ',
)
end
2012-08-19 09:07:05 +00:00
end
2011-10-16 17:49:58 +00:00
end
end
end