require 'bigdecimal' module Shoulda module Matchers module ActiveModel # The `validate_inclusion_of` matcher tests usage of the # `validates_inclusion_of` validation, asserting that an attribute can # take a whitelist of values and cannot take values outside of this list. # # If your whitelist is an array of values, use `in_array`: # # class Issue # include ActiveModel::Model # attr_accessor :state # # validates_inclusion_of :state, # in: ['open', 'resolved', 'unresolved'] # end # # # RSpec # describe Issue do # it do # should validate_inclusion_of(:state). # in_array(['open', 'resolved', 'unresolved']) # end # end # # # Minitest (Shoulda) # class IssueTest < ActiveSupport::TestCase # should validate_inclusion_of(:state). # in_array(['open', 'resolved', 'unresolved']) # end # # If your whitelist is a range of values, use `in_range`: # # class Issue # include ActiveModel::Model # attr_accessor :priority # # validates_inclusion_of :priority, in: 1..5 # end # # # RSpec # describe Issue do # it { should validate_inclusion_of(:state).in_range(1..5) } # end # # # Minitest (Shoulda) # class IssueTest < ActiveSupport::TestCase # should validate_inclusion_of(:state).in_range(1..5) # end # # #### Caveats # # We discourage using `validate_inclusion_of` with boolean columns. In # fact, there is never a case where a boolean column will be anything but # true, false, or nil, as ActiveRecord will type-cast an incoming value to # one of these three values. That means there isn't any way we can refute # this logic in a test. Hence, this will produce a warning: # # it do # should validate_inclusion_of(:imported). # in_array([true, false]) # end # # The only case where `validate_inclusion_of` *could* be appropriate is # for ensuring that a boolean column accepts nil, but we recommend # using `allow_value` instead, like this: # # it { should allow_value(nil).for(:imported) } # # #### Qualifiers # # Use `on` if your validation applies only under a certain context. # # class Issue # include ActiveModel::Model # attr_accessor :severity # # validates_inclusion_of :severity, # in: %w(low medium high), # on: :create # end # # # RSpec # describe Issue do # it do # should validate_inclusion_of(:severity). # in_array(%w(low medium high)). # on(:create) # end # end # # # Minitest (Shoulda) # class IssueTest < ActiveSupport::TestCase # should validate_inclusion_of(:severity). # in_array(%w(low medium high)). # on(:create) # end # # ##### with_message # # Use `with_message` if you are using a custom validation message. # # class Issue # include ActiveModel::Model # attr_accessor :severity # # validates_inclusion_of :severity, # in: %w(low medium high), # message: 'Severity must be low, medium, or high' # end # # # RSpec # describe Issue do # it do # should validate_inclusion_of(:severity). # in_array(%w(low medium high)). # with_message('Severity must be low, medium, or high') # end # end # # # Minitest (Shoulda) # class IssueTest < ActiveSupport::TestCase # should validate_inclusion_of(:severity). # in_array(%w(low medium high)). # with_message('Severity must be low, medium, or high') # end # # ##### with_low_message # # Use `with_low_message` if you have a custom validation message for when # a given value is too low. # # class Person # include ActiveModel::Model # attr_accessor :age # # validate :age_must_be_valid # # private # # def age_must_be_valid # if age < 65 # self.errors.add :age, 'You do not receive any benefits' # end # end # end # # # RSpec # describe Person do # it do # should validate_inclusion_of(:age). # in_range(0..65). # with_low_message('You do not receive any benefits') # end # end # # # Minitest (Shoulda) # class PersonTest < ActiveSupport::TestCase # should validate_inclusion_of(:age). # in_range(0..65). # with_low_message('You do not receive any benefits') # end # # ##### with_high_message # # Use `with_high_message` if you have a custom validation message for # when a given value is too high. # # class Person # include ActiveModel::Model # attr_accessor :age # # validate :age_must_be_valid # # private # # def age_must_be_valid # if age > 21 # self.errors.add :age, "You're too old for this stuff" # end # end # end # # # RSpec # describe Person do # it do # should validate_inclusion_of(:age). # in_range(0..21). # with_high_message("You're too old for this stuff") # end # end # # # Minitest (Shoulda) # class PersonTest < ActiveSupport::TestCase # should validate_inclusion_of(:age). # in_range(0..21). # with_high_message("You're too old for this stuff") # end # # ##### allow_nil # # Use `allow_nil` to assert that the attribute allows nil. # # class Issue # include ActiveModel::Model # attr_accessor :state # # validates_presence_of :state # validates_inclusion_of :state, # in: ['open', 'resolved', 'unresolved'], # allow_nil: true # end # # # RSpec # describe Issue do # it do # should validate_inclusion_of(:state). # in_array(['open', 'resolved', 'unresolved']). # allow_nil # end # end # # # Minitest (Shoulda) # class IssueTest < ActiveSupport::TestCase # should validate_inclusion_of(:state). # in_array(['open', 'resolved', 'unresolved']). # allow_nil # end # # ##### allow_blank # # Use `allow_blank` to assert that the attribute allows blank. # # class Issue # include ActiveModel::Model # attr_accessor :state # # validates_presence_of :state # validates_inclusion_of :state, # in: ['open', 'resolved', 'unresolved'], # allow_blank: true # end # # # RSpec # describe Issue do # it do # should validate_inclusion_of(:state). # in_array(['open', 'resolved', 'unresolved']). # allow_blank # end # end # # # Minitest (Shoulda) # class IssueTest < ActiveSupport::TestCase # should validate_inclusion_of(:state). # in_array(['open', 'resolved', 'unresolved']). # allow_blank # end # # @return [ValidateInclusionOfMatcher] # def validate_inclusion_of(attr) ValidateInclusionOfMatcher.new(attr) end # @private class ValidateInclusionOfMatcher < ValidationMatcher ARBITRARY_OUTSIDE_STRING = 'shoulda-matchers test string' ARBITRARY_OUTSIDE_FIXNUM = 123456789 ARBITRARY_OUTSIDE_DECIMAL = BigDecimal.new('0.123456789') ARBITRARY_OUTSIDE_DATE = Date.jd(9999999) ARBITRARY_OUTSIDE_DATETIME = DateTime.jd(9999999) ARBITRARY_OUTSIDE_TIME = Time.at(9999999999) BOOLEAN_ALLOWS_BOOLEAN_MESSAGE = <