moduleShouldamoduleMatchersmoduleActiveModel# The `validate_numericality_of` matcher tests usage of the# `validates_numericality_of` validation.## class Person# include ActiveModel::Model# attr_accessor :gpa## validates_numericality_of :gpa# end## # RSpec# RSpec.describe Person, type: :model do# it { should validate_numericality_of(:gpa) }# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:gpa)# end## #### Qualifiers## ##### on## Use `on` if your validation applies only under a certain context.## class Person# include ActiveModel::Model# attr_accessor :number_of_dependents## validates_numericality_of :number_of_dependents, on: :create# end## # RSpec# RSpec.describe Person, type: :model do# it do# should validate_numericality_of(:number_of_dependents).# on(:create)# end# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:number_of_dependents).on(:create)# end## ##### only_integer## Use `only_integer` to test usage of the `:only_integer` option. This# asserts that your attribute only allows integer numbers and disallows# non-integer ones.## class Person# include ActiveModel::Model# attr_accessor :age## validates_numericality_of :age, only_integer: true# end## # RSpec# RSpec.describe Person, type: :model do# it { should validate_numericality_of(:age).only_integer }# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:age).only_integer# end## ##### is_less_than## Use `is_less_than` to test usage of the the `:less_than` option. This# asserts that the attribute can take a number which is less than the# given value and cannot take a number which is greater than or equal to# it.## class Person# include ActiveModel::Model# attr_accessor :number_of_cars## validates_numericality_of :number_of_cars, less_than: 2# end## # RSpec# RSpec.describe Person, type: :model do# it do# should validate_numericality_of(:number_of_cars).# is_less_than(2)# end# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:number_of_cars).# is_less_than(2)# end## ##### is_less_than_or_equal_to## Use `is_less_than_or_equal_to` to test usage of the# `:less_than_or_equal_to` option. This asserts that the attribute can# take a number which is less than or equal to the given value and cannot# take a number which is greater than it.## class Person# include ActiveModel::Model# attr_accessor :birth_year## validates_numericality_of :birth_year, less_than_or_equal_to: 1987# end## # RSpec# RSpec.describe Person, type: :model do# it do# should validate_numericality_of(:birth_year).# is_less_than_or_equal_to(1987)# end# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:birth_year).# is_less_than_or_equal_to(1987)# end## ##### is_equal_to## Use `is_equal_to` to test usage of the `:equal_to` option. This asserts# that the attribute can take a number which is equal to the given value# and cannot take a number which is not equal.## class Person# include ActiveModel::Model# attr_accessor :weight## validates_numericality_of :weight, equal_to: 150# end## # RSpec# RSpec.describe Person, type: :model do# it { should validate_numericality_of(:weight).is_equal_to(150) }# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:weight).is_equal_to(150)# end## ##### is_greater_than_or_equal_to## Use `is_greater_than_or_equal_to` to test usage of the# `:greater_than_or_equal_to` option. This asserts that the attribute can# take a number which is greater than or equal to the given value and# cannot take a number which is less than it.## class Person# include ActiveModel::Model# attr_accessor :height## validates_numericality_of :height, greater_than_or_equal_to: 55# end## # RSpec# RSpec.describe Person, type: :model do# it do# should validate_numericality_of(:height).# is_greater_than_or_equal_to(55)# end# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:height).# is_greater_than_or_equal_to(55)# end## ##### is_greater_than## Use `is_greater_than` to test usage of the `:greater_than` option.# This asserts that the attribute can take a number which is greater than# the given value and cannot take a number less than or equal to it.## class Person# include ActiveModel::Model# attr_accessor :legal_age## validates_numericality_of :legal_age, greater_than: 21# end## # RSpec# RSpec.describe Person, type: :model do# it do# should validate_numericality_of(:legal_age).# is_greater_than(21)# end# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:legal_age).# is_greater_than(21)# end## ##### even## Use `even` to test usage of the `:even` option. This asserts that the# attribute can take odd numbers and cannot take even ones.## class Person# include ActiveModel::Model# attr_accessor :birth_month## validates_numericality_of :birth_month, even: true# end## # RSpec# RSpec.describe Person, type: :model do# it { should validate_numericality_of(:birth_month).even }# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:birth_month).even# end## ##### odd## Use `odd` to test usage of the `:odd` option. This asserts that the# attribute can take a number which is odd and cannot take a number which# is even.## class Person# include ActiveModel::Model# attr_accessor :birth_day## validates_numericality_of :birth_day, odd: true# end## # RSpec# RSpec.describe Person, type: :model do# it { should validate_numericality_of(:birth_day).odd }# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:birth_day).odd# end## ##### with_message## Use `with_message` if you are using a custom validation message.## class Person# include ActiveModel::Model# attr_accessor :number_of_dependents## validates_numericality_of :number_of_dependents,# message: 'Number of dependents must be a number'# end## # RSpec# RSpec.describe Person, type: :model do# it do# should validate_numericality_of(:number_of_dependents).# with_message('Number of dependents must be a number')# end# end## # Minitest (Shoulda)# class PersonTest < ActiveSupport::TestCase# should validate_numericality_of(:number_of_dependents).# with_message('Number of dependents must be a number')# end## ##### allow_nil## Use `allow_nil` to assert that the attribute allows nil.## class Post# include ActiveModel::Model# attr_accessor :age## validates_numericality_of :age, allow_nil: true# end## # RSpec# RSpec.describe Post, type: :model do# it { should validate_numericality_of(:age).allow_nil }# end## # Minitest (Shoulda)# class PostTest < ActiveSupport::TestCase# should validate_numericality_of(:age).allow_nil# end## @return [ValidateNumericalityOfMatcher]#defvalidate_numericality_of(attr)ValidateNumericalityOfMatcher.new(attr)end# @privateclassValidateNumericalityOfMatcherNUMERIC_NAME='number'NON_NUMERIC_VALUE='abcd'DEFAULT_DIFF_TO_COMPARE=1includeQualifiers::IgnoringInterferenceByWriterattr_reader:diff_to_comparedefinitialize(attribute)super@attribute=attribute@submatchers=[]@diff_to_compare=DEFAULT_DIFF_TO_COMPARE@expects_custom_validation_message=false@expects_to_allow_nil=false@expects_strict=false@allowed_type_adjective=nil@allowed_type_name='number'@context=nil@expected_message=nilenddefstrict@expects_strict=trueselfenddefexpects_strict?@expects_strictenddefonly_integerprepare_submatcher(NumericalityMatchers::OnlyIntegerMatcher.new(self,@attribute))selfenddefallow_nil@expects_to_allow_nil=trueprepare_submatcher(AllowValueMatcher.new(nil).for(@attribute).with_message(:not_a_number))selfenddefexpects_to_allow_nil?@expects_to_allow_nilenddefoddprepare_submatcher(NumericalityMatchers::OddNumberMatcher.new(self,@attribute))selfenddefevenprepare_submatcher(NumericalityMatchers::EvenNumberMatcher.new(self,@attribute))selfenddefis_greater_than(value)prepare_submatcher(comparison_matcher_for(value,:>).for(@attribute))selfenddefis_greater_than_or_equal_to(value)prepare_submatcher(comparison_matcher_for(value,:>=).for(@attribute))selfenddefis_equal_to(value)prepare_submatcher(comparison_matcher_for(value,:==).for(@attribute))selfenddefis_less_than(value)prepare_submatcher(comparison_matcher_for(value,:<).for(@attribute))selfenddefis_less_than_or_equal_to(value)prepare_submatcher(comparison_matcher_for(value,:<=).for(@attribute))selfenddefwith_message(message)@expects_custom_validation_message=true@expected_message=messageselfenddefexpects_custom_validation_message?@expects_custom_validation_messageenddefon(context)@context=contextselfenddefmatches?(subject)matches_or_does_not_match?(subject)first_submatcher_that_fails_to_match.nil?enddefdoes_not_match?(subject)matches_or_does_not_match?(subject)first_submatcher_that_fails_to_not_match.nil?enddefsimple_descriptiondescription=''description<<"validate that :#{@attribute} looks like "description<<Shoulda::Matchers::Util.a_or_an(full_allowed_type)ifcomparison_descriptions.present?description<<' '+comparison_descriptionsenddescriptionenddefdescriptionValidationMatcher::BuildDescription.call(self,simple_description)enddeffailure_messageoverall_failure_message.dup.tapdo|message|message<<"\n"message<<failure_message_for_first_submatcher_that_fails_to_matchendenddeffailure_message_when_negatedoverall_failure_message_when_negated.dup.tapdo|message|message<<"\n"message<<failure_message_for_first_submatcher_that_fails_to_not_matchendenddefgiven_numeric_column?attribute_is_active_record_column?&&[:integer,:float,:decimal].include?(column_type)endprivatedefmatches_or_does_not_match?(subject)@subject=subject@number_of_submatchers=@submatchers.sizeadd_disallow_value_matcherqualify_submatchersenddefoverall_failure_messageShoulda::Matchers.word_wrap("Expected #{model.name} to #{description}, but this could not "+'be proved.')enddefoverall_failure_message_when_negatedShoulda::Matchers.word_wrap("Expected #{model.name} not to #{description}, but this could not "+'be proved.')enddefattribute_is_active_record_column?columns_hash.key?(@attribute.to_s)enddefcolumn_typecolumns_hash[@attribute.to_s].typeenddefcolumns_hashif@subject.class.respond_to?(:columns_hash)@subject.class.columns_hashelse{}endenddefadd_disallow_value_matcherdisallow_value_matcher=DisallowValueMatcher.new(NON_NUMERIC_VALUE).for(@attribute).with_message(:not_a_number)add_submatcher(disallow_value_matcher)enddefprepare_submatcher(submatcher)add_submatcher(submatcher)submatcherenddefcomparison_matcher_for(value,operator)NumericalityMatchers::ComparisonMatcher.new(self,value,operator).for(@attribute)enddefadd_submatcher(submatcher)ifsubmatcher.respond_to?(:allowed_type_name)@allowed_type_name=submatcher.allowed_type_nameendifsubmatcher.respond_to?(:allowed_type_adjective)@allowed_type_adjective=submatcher.allowed_type_adjectiveendifsubmatcher.respond_to?(:diff_to_compare)@diff_to_compare=[@diff_to_compare,submatcher.diff_to_compare].maxend@submatchers<<submatcherenddefqualify_submatchers@submatchers.eachdo|submatcher|if@expects_strictsubmatcher.strict(@expects_strict)endif@expected_message.present?submatcher.with_message(@expected_message)endif@contextsubmatcher.on(@context)endsubmatcher.ignoring_interference_by_writer(ignore_interference_by_writer)endenddefnumber_of_submatchers_for_failure_messageifhas_been_qualified?@submatchers.size-1else@submatchers.sizeendenddefhas_been_qualified?@submatchers.any?do|submatcher|Shoulda::Matchers::RailsShim.parent_of(submatcher.class)==NumericalityMatchersendenddeffirst_submatcher_that_fails_to_match@_failing_submatchers||=@submatchers.detectdo|submatcher|!submatcher.matches?(@subject)endenddeffirst_submatcher_that_fails_to_not_match@_failing_submatchers||=@submatchers.detectdo|submatcher|!submatcher.does_not_match?(@subject)endenddeffailure_message_for_first_submatcher_that_fails_to_matchbuild_submatcher_failure_message_for(first_submatcher_that_fails_to_match,:failure_message)enddeffailure_message_for_first_submatcher_that_fails_to_not_matchbuild_submatcher_failure_message_for(first_submatcher_that_fails_to_not_match,:failure_message_when_negated)enddefbuild_submatcher_failure_message_for(submatcher,failure_message_method)failure_message=submatcher.public_send(failure_message_method)submatcher_description=submatcher.simple_description.sub(/\bvalidate that\b/,'validates').sub(/\bdisallow\b/,'disallows').sub(/\ballow\b/,'allows')submatcher_message=ifnumber_of_submatchers_for_failure_message>1"In checking that #{model.name}#{submatcher_description}, "+failure_message[0].downcase+failure_message[1..-1]elsefailure_messageendShoulda::Matchers.word_wrap(submatcher_message,indent: 2)enddeffull_allowed_type"#{@allowed_type_adjective}#{@allowed_type_name}".stripenddefcomparison_descriptionsdescription_array=submatcher_comparison_descriptionsdescription_array.empty??'':submatcher_comparison_descriptions.join(' and ')enddefsubmatcher_comparison_descriptions@submatchers.inject([])do|arr,submatcher|ifsubmatcher.respond_to?:comparison_descriptionarr<<submatcher.comparison_descriptionendarrendenddefmodel@subject.classendendendendend