lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb



module Shoulda # :nodoc:
  module Matchers
    module ActiveModel # :nodoc:
      module NumericalityMatchers
        # Examples:
        #   it { should validate_numericality_of(:attr).
        #                 is_greater_than(6).
        #                 less_than(20)...(and so on) }
        class ComparisonMatcher < ValidationMatcher
          DIFF_TO_COMPARE_PRECISION_CHANGE_NUMBER = 2**34
          attr_reader :diff_to_compare

          def initialize(numericality_matcher, value, operator)
            unless numericality_matcher.respond_to? :diff_to_compare
              raise ArgumentError, 'numericality_matcher is invalid'
            end
            @numericality_matcher = numericality_matcher
            @value = value
            @operator = operator
            @message = nil
            @diff_to_compare = value.abs < DIFF_TO_COMPARE_PRECISION_CHANGE_NUMBER ? 0.000_001 : 1
          end

          def for(attribute)
            @attribute = attribute
            self
          end

          def matches?(subject)
            @subject = subject
            all_bounds_correct?
          end

          def with_message(message)
            @message = message
          end

          def comparison_description
            "#{expectation} #{@value}"
          end

          private

          def comparison_combos
            allow = :allows_value_of
            disallow = :disallows_value_of
            checker_types =
              case @operator
                when :> then [allow, disallow, disallow]
                when :>= then [allow, allow, disallow]
                when :== then [disallow, allow, disallow]
                when :< then [disallow, disallow, allow]
                when :<= then [disallow, allow, allow]
              end
            diffs_to_compare.zip(checker_types)
          end

          def diffs_to_compare
            diff = @numericality_matcher.diff_to_compare
            [diff, 0, -diff]
          end

          def expectation
            case @operator
              when :> then "greater than"
              when :>= then "greater than or equal to"
              when :== then "equal to"
              when :< then "less than"
              when :<= then "less than or equal to"
            end
          end

          def all_bounds_correct?
            comparison_combos.all? do |diff, checker_type|
              __send__(checker_type, @value + diff, @message)
            end
          end
        end
      end
    end
  end
end