Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement comparison of matcher #1552

Merged
Merged
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ about any of them, make sure to [consult the documentation][rubydocs]!
tests usage of `validates_numericality_of`.
* **[validate_presence_of](lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb)**
tests usage of `validates_presence_of`.
* **[validate_comparison_of](lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb)**
tests usage of `validates_comparison_of`.

### ActiveRecord matchers

Expand Down
3 changes: 2 additions & 1 deletion lib/shoulda/matchers/active_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
require 'shoulda/matchers/active_model/validate_confirmation_of_matcher'
require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
require 'shoulda/matchers/active_model/validate_comparison_of_matcher'
require 'shoulda/matchers/active_model/comparison_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/comparison_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/odd_number_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/even_number_matcher'
require 'shoulda/matchers/active_model/numericality_matchers/only_integer_matcher'
Expand Down
162 changes: 162 additions & 0 deletions lib/shoulda/matchers/active_model/comparison_matcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
require 'active_support/core_ext/module/delegation'

module Shoulda
module Matchers
module ActiveModel
# @private
class ComparisonMatcher < ValidationMatcher
ERROR_MESSAGES = {
:> => {
label: :greater_than,
assertions: [false, false, true],
},
:>= => {
label: :greater_than_or_equal_to,
assertions: [false, true, true],
},
:< => {
label: :less_than,
assertions: [true, false, false],
},
:<= => {
label: :less_than_or_equal_to,
assertions: [true, true, false],
},
:== => {
label: :equal_to,
assertions: [false, true, false],
},
:!= => {
label: :other_than,
assertions: [true, false, true],
},
}.freeze

delegate :failure_message, :failure_message_when_negated, to: :comparison_submatchers

def initialize(matcher, value, operator)
super(nil)
unless matcher.respond_to? :diff_to_compare
raise ArgumentError, 'matcher is invalid'
end

@matcher = matcher
@value = value
@operator = operator
@message = ERROR_MESSAGES[operator][:label]
end

def simple_description
description = ''

if expects_strict?
description << ' strictly'
end

description +
"disallow :#{attribute} from being a number that is not " +
"#{comparison_expectation} #{@value}"
end

def for(attribute)
@attribute = attribute
self
end

def with_message(message)
@expects_custom_validation_message = true
@message = message
self
end

def expects_custom_validation_message?
@expects_custom_validation_message
end

def matches?(subject)
@subject = subject
comparison_submatchers.matches?(subject)
end

def does_not_match?(subject)
@subject = subject
comparison_submatchers.does_not_match?(subject)
end

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

def comparison_submatchers
@_comparison_submatchers ||=
NumericalityMatchers::Submatchers.new(build_comparison_submatchers)
end

private

def build_comparison_submatchers
comparison_combos.map do |diff, submatcher_method_name|
matcher = __send__(submatcher_method_name, diff, nil)
matcher.with_message(@message, values: { count: option_value })
matcher
end
end

def comparison_combos
diffs_to_compare.zip(submatcher_method_names)
end

def submatcher_method_names
assertions.map do |value|
if value
:allow_value_matcher
else
:disallow_value_matcher
end
end
end

def assertions
ERROR_MESSAGES[@operator][:assertions]
end

def option_value
if defined?(@_option_value)
@_option_value
else
@_option_value =
case @value
when Proc then @value.call(@subject)
when Symbol then @subject.send(@value)
else @value
end
end
end

def diffs_to_compare
diff_to_compare = @matcher.diff_to_compare
values = case option_value
when String then diffs_when_string(diff_to_compare)
else [-1, 0, 1].map { |sign| option_value + (diff_to_compare * sign) }
end

if @matcher.given_numeric_column?
values
else
values.map(&:to_s)
end
end

def diffs_when_string(diff_to_compare)
[-1, 0, 1].map do |sign|
option_value[0..-2] + (option_value[-1].ord + diff_to_compare * sign).chr
end
end

def comparison_expectation
ERROR_MESSAGES[@operator][:label].to_s.tr('_', ' ')
end
end
end
end
end

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def submatcher_combos
end

def build_comparison_submatcher(value, operator)
NumericalityMatchers::ComparisonMatcher.new(@numericality_matcher, value, operator).
ComparisonMatcher.new(@numericality_matcher, value, operator).
for(@attribute).
with_message(@message).
on(@context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,44 @@ def matches?(subject)
failing_submatchers.empty?
end

def does_not_match?(subject)
@subject = subject
non_failing_submatchers.empty?
end

def failure_message
last_failing_submatcher.failure_message
failing_submatcher.failure_message
end

def failure_message_when_negated
last_failing_submatcher.failure_message_when_negated
non_failing_submatcher.failure_message_when_negated
end

def add(submatcher)
@submatchers << submatcher
end

def last_failing_submatcher
failing_submatchers.last
end

private

def failing_submatchers
@_failing_submatchers ||= @submatchers.reject do |submatcher|
submatcher.matches?(@subject)
end
end

def non_failing_submatchers
@_non_failing_submatchers ||= @submatchers.reject do |submatcher|
submatcher.does_not_match?(@subject)
end
end

def failing_submatcher
failing_submatchers.last
end

def non_failing_submatcher
non_failing_submatchers.last
end
end
end
end
Expand Down
Loading