shoulda-matchers 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/.gitignore +8 -7
  2. data/.travis.yml +4 -0
  3. data/Appraisals +8 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +77 -66
  7. data/MIT-LICENSE +1 -1
  8. data/NEWS.md +63 -1
  9. data/README.md +189 -33
  10. data/Rakefile +6 -5
  11. data/features/rails_integration.feature +1 -1
  12. data/features/step_definitions/rails_steps.rb +7 -6
  13. data/gemfiles/3.0.gemfile +2 -2
  14. data/gemfiles/3.0.gemfile.lock +14 -5
  15. data/gemfiles/3.1.gemfile +2 -2
  16. data/gemfiles/3.1.gemfile.lock +14 -5
  17. data/gemfiles/3.2.gemfile +2 -2
  18. data/gemfiles/3.2.gemfile.lock +16 -7
  19. data/gemfiles/4.0.0.gemfile +2 -2
  20. data/gemfiles/4.0.0.gemfile.lock +15 -6
  21. data/gemfiles/4.0.1.gemfile +2 -2
  22. data/gemfiles/4.0.1.gemfile.lock +15 -6
  23. data/gemfiles/4.1.gemfile +19 -0
  24. data/gemfiles/4.1.gemfile.lock +176 -0
  25. data/lib/shoulda/matchers.rb +17 -1
  26. data/lib/shoulda/matchers/action_controller.rb +4 -2
  27. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +100 -0
  28. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +1 -1
  29. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +4 -4
  30. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +1 -1
  31. data/lib/shoulda/matchers/action_controller/route_matcher.rb +12 -12
  32. data/lib/shoulda/matchers/action_controller/route_params.rb +1 -1
  33. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +2 -1
  34. data/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +167 -0
  35. data/lib/shoulda/matchers/active_model.rb +4 -2
  36. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +23 -5
  37. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +0 -4
  38. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +66 -14
  39. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +8 -8
  40. data/lib/shoulda/matchers/active_model/errors.rb +40 -0
  41. data/lib/shoulda/matchers/active_model/helpers.rb +6 -6
  42. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +33 -14
  43. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +26 -0
  44. data/lib/shoulda/matchers/active_model/numericality_matchers/{odd_even_number_matcher.rb → numeric_type_matcher.rb} +9 -20
  45. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +26 -0
  46. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +5 -21
  47. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +1 -1
  48. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +71 -22
  49. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -1
  50. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +25 -6
  51. data/lib/shoulda/matchers/active_record.rb +1 -0
  52. data/lib/shoulda/matchers/active_record/association_matcher.rb +67 -13
  53. data/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +40 -0
  54. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +24 -1
  55. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -1
  56. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +1 -1
  57. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +1 -1
  58. data/lib/shoulda/matchers/assertion_error.rb +7 -2
  59. data/lib/shoulda/matchers/error.rb +24 -0
  60. data/lib/shoulda/matchers/independent.rb +10 -0
  61. data/lib/shoulda/matchers/independent/delegate_matcher.rb +157 -0
  62. data/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb +34 -0
  63. data/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb +36 -0
  64. data/lib/shoulda/matchers/integrations/rspec.rb +13 -14
  65. data/lib/shoulda/matchers/integrations/test_unit.rb +11 -9
  66. data/lib/shoulda/matchers/version.rb +1 -1
  67. data/lib/shoulda/matchers/warn.rb +7 -0
  68. data/shoulda-matchers.gemspec +2 -1
  69. data/spec/shoulda/matchers/action_controller/callback_matcher_spec.rb +79 -0
  70. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +3 -3
  71. data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +11 -11
  72. data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +21 -21
  73. data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +10 -10
  74. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +45 -18
  75. data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +8 -8
  76. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +19 -19
  77. data/spec/shoulda/matchers/action_controller/route_params_spec.rb +6 -6
  78. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +11 -11
  79. data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +44 -44
  80. data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +205 -0
  81. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +24 -24
  82. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +37 -37
  83. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +17 -21
  84. data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +24 -24
  85. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +173 -67
  86. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +40 -40
  87. data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +20 -20
  88. data/spec/shoulda/matchers/active_model/helpers_spec.rb +27 -25
  89. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +126 -13
  90. data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
  91. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
  92. data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +27 -26
  93. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +15 -15
  94. data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +8 -8
  95. data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +9 -9
  96. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +229 -44
  97. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +44 -25
  98. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +110 -62
  99. data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +19 -19
  100. data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +30 -30
  101. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +378 -192
  102. data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +4 -0
  103. data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +33 -33
  104. data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +21 -17
  105. data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +8 -8
  106. data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +14 -14
  107. data/spec/shoulda/matchers/independent/delegate_matcher/stubbed_target_spec.rb +43 -0
  108. data/spec/shoulda/matchers/independent/delegate_matcher_spec.rb +184 -0
  109. data/spec/spec_helper.rb +4 -0
  110. data/spec/support/activemodel_helpers.rb +2 -2
  111. data/spec/support/capture_helpers.rb +19 -0
  112. data/spec/support/controller_builder.rb +22 -3
  113. data/spec/support/fail_with_message_including_matcher.rb +33 -0
  114. data/spec/support/model_builder.rb +1 -1
  115. data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
  116. data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
  117. data/spec/support/test_application.rb +23 -0
  118. metadata +90 -22
  119. checksums.yaml +0 -7
  120. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_even_number_matcher_spec.rb +0 -97
  121. data/spec/support/shared_examples/numerical_submatcher_spec.rb +0 -23
@@ -110,18 +110,18 @@ module Shoulda # :nodoc:
110
110
  def translate_messages!
111
111
  if Symbol === @short_message
112
112
  @short_message = default_error_message(@short_message,
113
- :model_name => @subject.class.to_s.underscore,
114
- :instance => @subject,
115
- :attribute => @attribute,
116
- :count => @options[:minimum])
113
+ model_name: @subject.class.to_s.underscore,
114
+ instance: @subject,
115
+ attribute: @attribute,
116
+ count: @options[:minimum])
117
117
  end
118
118
 
119
119
  if Symbol === @long_message
120
120
  @long_message = default_error_message(@long_message,
121
- :model_name => @subject.class.to_s.underscore,
122
- :instance => @subject,
123
- :attribute => @attribute,
124
- :count => @options[:maximum])
121
+ model_name: @subject.class.to_s.underscore,
122
+ instance: @subject,
123
+ attribute: @attribute,
124
+ count: @options[:maximum])
125
125
  end
126
126
  end
127
127
 
@@ -2,6 +2,46 @@ module Shoulda # :nodoc:
2
2
  module Matchers
3
3
  module ActiveModel # :nodoc:
4
4
  class CouldNotDetermineValueOutsideOfArray < RuntimeError; end
5
+
6
+ class NonNullableBooleanError < Shoulda::Matchers::Error; end
7
+
8
+ class CouldNotClearAttribute < Shoulda::Matchers::Error
9
+ def self.create(actual_value)
10
+ super(actual_value: actual_value)
11
+ end
12
+
13
+ attr_accessor :actual_value
14
+
15
+ def message
16
+ "Expected value to be nil, but was #{actual_value.inspect}."
17
+ end
18
+ end
19
+
20
+ class CouldNotSetPasswordError < Shoulda::Matchers::Error
21
+ def self.create(model)
22
+ super(model: model)
23
+ end
24
+
25
+ attr_accessor :model
26
+
27
+ def message
28
+ <<-EOT.strip
29
+ The validation failed because your #{model_name} model declares `has_secure_password`, and
30
+ `validate_presence_of` was called on a #{record_name} which has `password` already set to a value.
31
+ Please use a #{record_name} with an empty `password` instead.
32
+ EOT
33
+ end
34
+
35
+ private
36
+
37
+ def model_name
38
+ model.name
39
+ end
40
+
41
+ def record_name
42
+ model_name.humanize.downcase
43
+ end
44
+ end
5
45
  end
6
46
  end
7
47
  end
@@ -6,7 +6,7 @@ module Shoulda # :nodoc:
6
6
  obj.errors.map do |attribute, model|
7
7
  msg = "#{attribute} #{model}"
8
8
  if attribute.to_sym != :base && obj.respond_to?(attribute)
9
- msg << " (#{obj.send(attribute).inspect})"
9
+ msg << " (#{obj.__send__(attribute).inspect})"
10
10
  end
11
11
  msg
12
12
  end
@@ -19,10 +19,10 @@ module Shoulda # :nodoc:
19
19
  # instance is given.
20
20
  #
21
21
  # default_error_message(:blank)
22
- # default_error_message(:too_short, :count => 5)
23
- # default_error_message(:too_long, :count => 60)
24
- # default_error_message(:blank, :model_name => 'user', :attribute => 'name')
25
- # default_error_message(:blank, :instance => #<Model>, :attribute => 'name')
22
+ # default_error_message(:too_short, count: 5)
23
+ # default_error_message(:too_long, count: 60)
24
+ # default_error_message(:blank, model_name: 'user', attribute: 'name')
25
+ # default_error_message(:blank, instance: #<Model>, attribute: 'name')
26
26
  def default_error_message(key, options = {})
27
27
  model_name = options.delete(:model_name)
28
28
  attribute = options.delete(:attribute)
@@ -36,7 +36,7 @@ module Shoulda # :nodoc:
36
36
  :"errors.attributes.#{attribute}.#{key}",
37
37
  :"errors.messages.#{key}" ]
38
38
  I18n.translate(:"activerecord.errors.models.#{model_name}.attributes.#{attribute}.#{key}",
39
- { :default => default_translation }.merge(options))
39
+ { default: default_translation }.merge(options))
40
40
  end
41
41
  end
42
42
  end
@@ -7,7 +7,11 @@ module Shoulda # :nodoc:
7
7
  # is_greater_than(6).
8
8
  # less_than(20)...(and so on) }
9
9
  class ComparisonMatcher < ValidationMatcher
10
- def initialize(value, operator)
10
+ def initialize(numericality_matcher, value, operator)
11
+ unless numericality_matcher.respond_to? :diff_to_compare
12
+ raise ArgumentError, 'numericality_matcher is invalid'
13
+ end
14
+ @numericality_matcher = numericality_matcher
11
15
  @value = value
12
16
  @operator = operator
13
17
  @message = nil
@@ -20,27 +24,36 @@ module Shoulda # :nodoc:
20
24
 
21
25
  def matches?(subject)
22
26
  @subject = subject
23
- disallows_value_of(value_to_compare, @message)
24
- end
25
-
26
- def allowed_types
27
- 'integer'
27
+ all_bounds_correct?
28
28
  end
29
29
 
30
30
  def with_message(message)
31
31
  @message = message
32
32
  end
33
33
 
34
+ def comparison_description
35
+ "#{expectation} #{@value}"
36
+ end
37
+
34
38
  private
35
39
 
36
- def value_to_compare
37
- case @operator
38
- when :> then [@value, @value - 1].sample
39
- when :>= then @value - 1
40
- when :== then @value + 1
41
- when :< then [@value, @value + 1].sample
42
- when :<= then @value + 1
43
- end
40
+ def comparison_combos
41
+ allow = :allows_value_of
42
+ disallow = :disallows_value_of
43
+ checker_types =
44
+ case @operator
45
+ when :> then [allow, disallow, disallow]
46
+ when :>= then [allow, allow, disallow]
47
+ when :== then [disallow, allow, disallow]
48
+ when :< then [disallow, disallow, allow]
49
+ when :<= then [disallow, allow, allow]
50
+ end
51
+ diffs_to_compare.zip(checker_types)
52
+ end
53
+
54
+ def diffs_to_compare
55
+ diff = @numericality_matcher.diff_to_compare
56
+ [diff, 0, -diff]
44
57
  end
45
58
 
46
59
  def expectation
@@ -52,6 +65,12 @@ module Shoulda # :nodoc:
52
65
  when :<= then "less than or equal to"
53
66
  end
54
67
  end
68
+
69
+ def all_bounds_correct?
70
+ comparison_combos.all? do |diff, checker_type|
71
+ __send__(checker_type, @value + diff, @message)
72
+ end
73
+ end
55
74
  end
56
75
  end
57
76
  end
@@ -0,0 +1,26 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveModel # :nodoc:
4
+ module NumericalityMatchers
5
+ class EvenNumberMatcher < NumericTypeMatcher # :nodoc:
6
+ NON_EVEN_NUMBER_VALUE = 1
7
+
8
+ def initialize(attribute, options = {})
9
+ @attribute = attribute
10
+ @disallow_value_matcher = DisallowValueMatcher.new(NON_EVEN_NUMBER_VALUE).
11
+ for(@attribute).
12
+ with_message(:even)
13
+ end
14
+
15
+ def allowed_type
16
+ 'even numbers'
17
+ end
18
+
19
+ def diff_to_compare
20
+ 2
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,24 +2,9 @@ module Shoulda # :nodoc:
2
2
  module Matchers
3
3
  module ActiveModel # :nodoc:
4
4
  module NumericalityMatchers
5
- class OddEvenNumberMatcher # :nodoc:
6
- NON_EVEN_NUMBER_VALUE = 1
7
- NON_ODD_NUMBER_VALUE = 2
8
-
9
- def initialize(attribute, options = {})
10
- @attribute = attribute
11
- options[:odd] ||= true
12
- options[:even] ||= false
13
-
14
- if options[:odd] && !options[:even]
15
- @disallow_value_matcher = DisallowValueMatcher.new(NON_ODD_NUMBER_VALUE).
16
- for(@attribute).
17
- with_message(:odd)
18
- else
19
- @disallow_value_matcher = DisallowValueMatcher.new(NON_EVEN_NUMBER_VALUE).
20
- for(@attribute).
21
- with_message(:even)
22
- end
5
+ class NumericTypeMatcher
6
+ def initialize
7
+ raise NotImplementedError
23
8
  end
24
9
 
25
10
  def matches?(subject)
@@ -31,8 +16,12 @@ module Shoulda # :nodoc:
31
16
  self
32
17
  end
33
18
 
34
- def allowed_types
35
- 'integer'
19
+ def allowed_type
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def diff_to_compare
24
+ raise NotImplementedError
36
25
  end
37
26
 
38
27
  def failure_message
@@ -0,0 +1,26 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveModel # :nodoc:
4
+ module NumericalityMatchers
5
+ class OddNumberMatcher < NumericTypeMatcher # :nodoc:
6
+ NON_ODD_NUMBER_VALUE = 2
7
+
8
+ def initialize(attribute, options = {})
9
+ @attribute = attribute
10
+ @disallow_value_matcher = DisallowValueMatcher.new(NON_ODD_NUMBER_VALUE).
11
+ for(@attribute).
12
+ with_message(:odd)
13
+ end
14
+
15
+ def allowed_type
16
+ 'odd numbers'
17
+ end
18
+
19
+ def diff_to_compare
20
+ 2
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,9 +2,8 @@ module Shoulda # :nodoc:
2
2
  module Matchers
3
3
  module ActiveModel # :nodoc:
4
4
  module NumericalityMatchers
5
- class OnlyIntegerMatcher # :nodoc:
5
+ class OnlyIntegerMatcher < NumericTypeMatcher # :nodoc:
6
6
  NON_INTEGER_VALUE = 0.1
7
-
8
7
  def initialize(attribute)
9
8
  @attribute = attribute
10
9
  @disallow_value_matcher = DisallowValueMatcher.new(NON_INTEGER_VALUE).
@@ -12,28 +11,13 @@ module Shoulda # :nodoc:
12
11
  with_message(:not_an_integer)
13
12
  end
14
13
 
15
- def matches?(subject)
16
- @disallow_value_matcher.matches?(subject)
17
- end
18
-
19
- def with_message(message)
20
- @disallow_value_matcher.with_message(message)
21
- self
22
- end
23
-
24
- def allowed_types
25
- 'integer'
26
- end
27
-
28
- def failure_message
29
- @disallow_value_matcher.failure_message
14
+ def allowed_type
15
+ 'integers'
30
16
  end
31
- alias failure_message_for_should failure_message
32
17
 
33
- def failure_message_when_negated
34
- @disallow_value_matcher.failure_message_when_negated
18
+ def diff_to_compare
19
+ 1
35
20
  end
36
- alias failure_message_for_should_not failure_message_when_negated
37
21
  end
38
22
  end
39
23
  end
@@ -64,7 +64,7 @@ module Shoulda # :nodoc:
64
64
  def set_confirmation(val)
65
65
  setter = :"#{@confirmation_attribute}="
66
66
  if @subject.respond_to?(setter)
67
- @subject.send(setter, val)
67
+ @subject.__send__(setter, val)
68
68
  end
69
69
  end
70
70
 
@@ -10,66 +10,84 @@ module Shoulda # :nodoc:
10
10
  # * <tt>only_integer</tt> - allows only integer values
11
11
  # * <tt>odd</tt> - Specifies the value must be an odd number.
12
12
  # * <tt>even</tt> - Specifies the value must be an even number.
13
+ # * <tt>allow_nil</tt> - allows nil values
13
14
  #
14
15
  # Examples:
15
16
  # it { should validate_numericality_of(:price) }
16
17
  # it { should validate_numericality_of(:age).only_integer }
17
18
  # it { should validate_numericality_of(:frequency).odd }
18
19
  # it { should validate_numericality_of(:frequency).even }
20
+ # it { should validate_numericality_of(:rank).is_less_than_or_equal_to(10).allow_nil }
19
21
  #
20
22
  def validate_numericality_of(attr)
21
23
  ValidateNumericalityOfMatcher.new(attr)
22
24
  end
23
25
 
24
26
  class ValidateNumericalityOfMatcher
27
+ NUMERIC_NAME = 'numbers'
25
28
  NON_NUMERIC_VALUE = 'abcd'
29
+ DEFAULT_DIFF_TO_COMPARE = 0.000_000_000_001
30
+ attr_reader :diff_to_compare
26
31
 
27
32
  def initialize(attribute)
28
33
  @attribute = attribute
29
34
  @submatchers = []
30
-
35
+ @diff_to_compare = DEFAULT_DIFF_TO_COMPARE
31
36
  add_disallow_value_matcher
32
37
  end
33
38
 
34
39
  def only_integer
35
- add_submatcher(NumericalityMatchers::OnlyIntegerMatcher.new(@attribute))
40
+ prepare_submatcher(
41
+ NumericalityMatchers::OnlyIntegerMatcher.new(@attribute)
42
+ )
36
43
  self
37
44
  end
38
45
 
39
- def is_greater_than(value)
40
- add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :>).for(@attribute))
46
+ def allow_nil
47
+ prepare_submatcher(
48
+ AllowValueMatcher.new(nil)
49
+ .for(@attribute)
50
+ .with_message(:not_a_number)
51
+ )
41
52
  self
42
53
  end
43
54
 
44
- def is_greater_than_or_equal_to(value)
45
- add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :>=).for(@attribute))
55
+ def odd
56
+ prepare_submatcher(
57
+ NumericalityMatchers::OddNumberMatcher.new(@attribute)
58
+ )
46
59
  self
47
60
  end
48
61
 
49
- def is_equal_to(value)
50
- add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :==).for(@attribute))
62
+ def even
63
+ prepare_submatcher(
64
+ NumericalityMatchers::EvenNumberMatcher.new(@attribute)
65
+ )
51
66
  self
52
67
  end
53
68
 
54
- def is_less_than(value)
55
- add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :<).for(@attribute))
69
+ def is_greater_than(value)
70
+ prepare_submatcher(comparison_matcher_for(value, :>).for(@attribute))
56
71
  self
57
72
  end
58
73
 
59
- def is_less_than_or_equal_to(value)
60
- add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :<=).for(@attribute))
74
+ def is_greater_than_or_equal_to(value)
75
+ prepare_submatcher(comparison_matcher_for(value, :>=).for(@attribute))
61
76
  self
62
77
  end
63
78
 
64
- def odd
65
- odd_number_matcher = NumericalityMatchers::OddEvenNumberMatcher.new(@attribute, :odd => true)
66
- add_submatcher(odd_number_matcher)
79
+ def is_equal_to(value)
80
+ prepare_submatcher(comparison_matcher_for(value, :==).for(@attribute))
67
81
  self
68
82
  end
69
83
 
70
- def even
71
- even_number_matcher = NumericalityMatchers::OddEvenNumberMatcher.new(@attribute, :even => true)
72
- add_submatcher(even_number_matcher)
84
+ def is_less_than(value)
85
+ prepare_submatcher(comparison_matcher_for(value, :<).for(@attribute))
86
+ self
87
+ end
88
+
89
+ def is_less_than_or_equal_to(value)
90
+ prepare_submatcher(comparison_matcher_for(value, :<=).for(@attribute))
73
91
  self
74
92
  end
75
93
 
@@ -84,7 +102,7 @@ module Shoulda # :nodoc:
84
102
  end
85
103
 
86
104
  def description
87
- "only allow #{allowed_types} values for #{@attribute}"
105
+ "only allow #{allowed_types} for #{@attribute}#{comparison_descriptions}"
88
106
  end
89
107
 
90
108
  def failure_message
@@ -107,10 +125,27 @@ module Shoulda # :nodoc:
107
125
  add_submatcher(disallow_value_matcher)
108
126
  end
109
127
 
128
+ def prepare_submatcher(submatcher)
129
+ add_submatcher(submatcher)
130
+ if submatcher.respond_to?(:diff_to_compare)
131
+ update_diff_to_compare(submatcher)
132
+ end
133
+ end
134
+
135
+ def comparison_matcher_for(value, operator)
136
+ NumericalityMatchers::ComparisonMatcher
137
+ .new(self, value, operator)
138
+ .for(@attribute)
139
+ end
140
+
110
141
  def add_submatcher(submatcher)
111
142
  @submatchers << submatcher
112
143
  end
113
144
 
145
+ def update_diff_to_compare(matcher)
146
+ @diff_to_compare = [@diff_to_compare, matcher.diff_to_compare].max
147
+ end
148
+
114
149
  def submatchers_match?
115
150
  failing_submatchers.empty?
116
151
  end
@@ -128,12 +163,26 @@ module Shoulda # :nodoc:
128
163
  end
129
164
 
130
165
  def allowed_types
131
- allowed = ['numeric'] + submatcher_allowed_types
132
- allowed.join(', ')
166
+ allowed_array = submatcher_allowed_types
167
+ allowed_array.empty? ? NUMERIC_NAME : allowed_array.join(', ')
133
168
  end
134
169
 
135
170
  def submatcher_allowed_types
136
- @submatchers.map(&:allowed_types).reject(&:empty?)
171
+ @submatchers.inject([]){|m, s| m << s.allowed_type if s.respond_to?(:allowed_type); m }
172
+ end
173
+
174
+ def comparison_descriptions
175
+ description_array = submatcher_comparison_descriptions
176
+ description_array.empty? ? '' : ' which are ' + submatcher_comparison_descriptions.join(' and ')
177
+ end
178
+
179
+ def submatcher_comparison_descriptions
180
+ @submatchers.inject([]) do |arr, submatcher|
181
+ if submatcher.respond_to? :comparison_description
182
+ arr << submatcher.comparison_description
183
+ end
184
+ arr
185
+ end
137
186
  end
138
187
  end
139
188
  end