mcmire-shoulda-matchers 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +32 -0
  4. data/.yardopts +7 -0
  5. data/Appraisals +45 -0
  6. data/CONTRIBUTING.md +41 -0
  7. data/Gemfile +31 -0
  8. data/Gemfile.lock +166 -0
  9. data/MIT-LICENSE +22 -0
  10. data/NEWS.md +299 -0
  11. data/README.md +163 -0
  12. data/Rakefile +116 -0
  13. data/doc_config/gh-pages/index.html.erb +9 -0
  14. data/doc_config/yard/setup.rb +22 -0
  15. data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +5967 -0
  16. data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +12 -0
  17. data/doc_config/yard/templates/default/fulldoc/html/css/global.css +45 -0
  18. data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +69 -0
  19. data/doc_config/yard/templates/default/fulldoc/html/css/style.css +283 -0
  20. data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +32 -0
  21. data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +1 -0
  22. data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +8 -0
  23. data/doc_config/yard/templates/default/fulldoc/html/js/app.js +300 -0
  24. data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +1 -0
  25. data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +289 -0
  26. data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +6 -0
  27. data/doc_config/yard/templates/default/fulldoc/html/setup.rb +8 -0
  28. data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +14 -0
  29. data/doc_config/yard/templates/default/layout/html/fonts.erb +1 -0
  30. data/doc_config/yard/templates/default/layout/html/layout.erb +23 -0
  31. data/doc_config/yard/templates/default/layout/html/search.erb +13 -0
  32. data/doc_config/yard/templates/default/layout/html/setup.rb +8 -0
  33. data/doc_config/yard/templates/default/method_details/html/source.erb +10 -0
  34. data/doc_config/yard/templates/default/module/html/box_info.erb +31 -0
  35. data/features/rails_integration.feature +113 -0
  36. data/features/step_definitions/rails_steps.rb +162 -0
  37. data/features/support/env.rb +5 -0
  38. data/gemfiles/3.0.gemfile +24 -0
  39. data/gemfiles/3.0.gemfile.lock +150 -0
  40. data/gemfiles/3.1.gemfile +27 -0
  41. data/gemfiles/3.1.gemfile.lock +173 -0
  42. data/gemfiles/3.2.gemfile +27 -0
  43. data/gemfiles/3.2.gemfile.lock +171 -0
  44. data/gemfiles/4.0.0.gemfile +28 -0
  45. data/gemfiles/4.0.0.gemfile.lock +172 -0
  46. data/gemfiles/4.0.1.gemfile +28 -0
  47. data/gemfiles/4.0.1.gemfile.lock +172 -0
  48. data/lib/shoulda-matchers.rb +1 -0
  49. data/lib/shoulda/matchers.rb +11 -0
  50. data/lib/shoulda/matchers/action_controller.rb +17 -0
  51. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +64 -0
  52. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +97 -0
  53. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +81 -0
  54. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +117 -0
  55. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +114 -0
  56. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +154 -0
  57. data/lib/shoulda/matchers/action_controller/route_matcher.rb +116 -0
  58. data/lib/shoulda/matchers/action_controller/route_params.rb +48 -0
  59. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +164 -0
  60. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +296 -0
  61. data/lib/shoulda/matchers/active_model.rb +30 -0
  62. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +167 -0
  63. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +314 -0
  64. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +46 -0
  65. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +160 -0
  66. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +417 -0
  67. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +337 -0
  68. data/lib/shoulda/matchers/active_model/errors.rb +10 -0
  69. data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
  70. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +92 -0
  71. data/lib/shoulda/matchers/active_model/helpers.rb +46 -0
  72. data/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
  73. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +75 -0
  74. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +27 -0
  75. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +41 -0
  76. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +27 -0
  77. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +26 -0
  78. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +112 -0
  79. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +77 -0
  80. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +121 -0
  81. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +380 -0
  82. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +89 -0
  83. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +372 -0
  84. data/lib/shoulda/matchers/active_model/validation_matcher.rb +97 -0
  85. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
  86. data/lib/shoulda/matchers/active_record.rb +22 -0
  87. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +204 -0
  88. data/lib/shoulda/matchers/active_record/association_matcher.rb +901 -0
  89. data/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
  90. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +41 -0
  91. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +41 -0
  92. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +81 -0
  93. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +65 -0
  94. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +94 -0
  95. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
  96. data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +41 -0
  97. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +63 -0
  98. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +261 -0
  99. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +149 -0
  100. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +72 -0
  101. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +181 -0
  102. data/lib/shoulda/matchers/assertion_error.rb +19 -0
  103. data/lib/shoulda/matchers/error.rb +6 -0
  104. data/lib/shoulda/matchers/integrations/rspec.rb +20 -0
  105. data/lib/shoulda/matchers/integrations/test_unit.rb +30 -0
  106. data/lib/shoulda/matchers/rails_shim.rb +50 -0
  107. data/lib/shoulda/matchers/version.rb +6 -0
  108. data/lib/shoulda/matchers/warn.rb +8 -0
  109. data/shoulda-matchers.gemspec +23 -0
  110. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +22 -0
  111. data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +42 -0
  112. data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +78 -0
  113. data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +63 -0
  114. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +63 -0
  115. data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +31 -0
  116. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +70 -0
  117. data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
  118. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +51 -0
  119. data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +153 -0
  120. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +111 -0
  121. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +170 -0
  122. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +81 -0
  123. data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +95 -0
  124. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +320 -0
  125. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +166 -0
  126. data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +111 -0
  127. data/spec/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +20 -0
  128. data/spec/shoulda/matchers/active_model/helpers_spec.rb +158 -0
  129. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +169 -0
  130. data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
  131. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
  132. data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +57 -0
  133. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
  134. data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +41 -0
  135. data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +47 -0
  136. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +331 -0
  137. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +180 -0
  138. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +398 -0
  139. data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +127 -0
  140. data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +107 -0
  141. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +860 -0
  142. data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
  143. data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +111 -0
  144. data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +78 -0
  145. data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +41 -0
  146. data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +86 -0
  147. data/spec/spec_helper.rb +26 -0
  148. data/spec/support/active_model_versions.rb +13 -0
  149. data/spec/support/active_resource_builder.rb +29 -0
  150. data/spec/support/activemodel_helpers.rb +19 -0
  151. data/spec/support/capture_helpers.rb +19 -0
  152. data/spec/support/class_builder.rb +42 -0
  153. data/spec/support/controller_builder.rb +74 -0
  154. data/spec/support/fail_with_message_including_matcher.rb +33 -0
  155. data/spec/support/fail_with_message_matcher.rb +32 -0
  156. data/spec/support/i18n_faker.rb +10 -0
  157. data/spec/support/mailer_builder.rb +10 -0
  158. data/spec/support/model_builder.rb +81 -0
  159. data/spec/support/rails_versions.rb +18 -0
  160. data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
  161. data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
  162. data/spec/support/test_application.rb +120 -0
  163. data/yard.watchr +5 -0
  164. metadata +281 -0
@@ -0,0 +1,121 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # The `validate_confirmation_of` matcher tests usage of the
5
+ # `validates_confirmation_of` validation.
6
+ #
7
+ # class User
8
+ # include ActiveModel::Model
9
+ #
10
+ # validates_confirmation_of :email
11
+ # end
12
+ #
13
+ # # RSpec
14
+ # describe User do
15
+ # it { should validate_confirmation_of(:email) }
16
+ # end
17
+ #
18
+ # # Test::Unit
19
+ # class UserTest < ActiveSupport::TestCase
20
+ # should validate_confirmation_of(:email)
21
+ # end
22
+ #
23
+ # #### Qualifiers
24
+ #
25
+ # ##### with_message
26
+ #
27
+ # Use `with_message` if you are using a custom validation message.
28
+ #
29
+ # class User
30
+ # include ActiveModel::Model
31
+ #
32
+ # validates_confirmation_of :password,
33
+ # message: 'Please re-enter your password'
34
+ # end
35
+ #
36
+ # # RSpec
37
+ # describe User do
38
+ # it do
39
+ # should validate_confirmation_of(:password).
40
+ # with_message('Please re-enter your password')
41
+ # end
42
+ # end
43
+ #
44
+ # # Test::Unit
45
+ # class UserTest < ActiveSupport::TestCase
46
+ # should validate_confirmation_of(:password).
47
+ # with_message('Please re-enter your password')
48
+ # end
49
+ #
50
+ # @return [ValidateConfirmationOfMatcher]
51
+ #
52
+ def validate_confirmation_of(attr)
53
+ ValidateConfirmationOfMatcher.new(attr)
54
+ end
55
+
56
+ # @private
57
+ class ValidateConfirmationOfMatcher < ValidationMatcher
58
+ include Helpers
59
+
60
+ attr_reader :attribute, :confirmation_attribute
61
+
62
+ def initialize(attribute)
63
+ @attribute = attribute
64
+ @confirmation_attribute = "#{attribute}_confirmation"
65
+ end
66
+
67
+ def with_message(message)
68
+ @message = message if message
69
+ self
70
+ end
71
+
72
+ def description
73
+ "require #{@confirmation_attribute} to match #{@attribute}"
74
+ end
75
+
76
+ def matches?(subject)
77
+ super(subject)
78
+ @message ||= :confirmation
79
+
80
+ disallows_different_value &&
81
+ allows_same_value &&
82
+ allows_missing_confirmation
83
+ end
84
+
85
+ private
86
+
87
+ def disallows_different_value
88
+ set_confirmation('some value')
89
+ disallows_value_of('different value') do |matcher|
90
+ matcher.with_message(@message, against: error_attribute)
91
+ end
92
+ end
93
+
94
+ def allows_same_value
95
+ set_confirmation('same value')
96
+ allows_value_of('same value') do |matcher|
97
+ matcher.with_message(@message, against: error_attribute)
98
+ end
99
+ end
100
+
101
+ def allows_missing_confirmation
102
+ set_confirmation(nil)
103
+ allows_value_of('any value') do |matcher|
104
+ matcher.with_message(@message, against: error_attribute)
105
+ end
106
+ end
107
+
108
+ def set_confirmation(val)
109
+ setter = :"#{@confirmation_attribute}="
110
+ if @subject.respond_to?(setter)
111
+ @subject.__send__(setter, val)
112
+ end
113
+ end
114
+
115
+ def error_attribute
116
+ RailsShim.validates_confirmation_of_error_attribute(self)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,380 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # The `validate_numericality_of` matcher tests usage of the
5
+ # `validates_numericality_of` validation.
6
+ #
7
+ # class Person < ActiveRecord::Base
8
+ # validates_numericality_of :gpa
9
+ # end
10
+ #
11
+ # # RSpec
12
+ # describe Person do
13
+ # it { should validate_numericality_of(:gpa) }
14
+ # end
15
+ #
16
+ # # Test::Unit
17
+ # class PersonTest < ActiveSupport::TestCase
18
+ # should validate_numericality_of(:gpa)
19
+ # end
20
+ #
21
+ # #### Qualifiers
22
+ #
23
+ # ##### only_integer
24
+ #
25
+ # Use `only_integer` to test usage of the `:only_integer` option. This
26
+ # asserts that your attribute only allows integer numbers and disallows
27
+ # non-integer ones.
28
+ #
29
+ # class Person
30
+ # include ActiveModel::Model
31
+ #
32
+ # validates_numericality_of :age, only_integer: true
33
+ # end
34
+ #
35
+ # # RSpec
36
+ # describe Person do
37
+ # it { should validate_numericality_of(:age).only_integer }
38
+ # end
39
+ #
40
+ # # Test::Unit
41
+ # class PersonTest < ActiveSupport::TestCase
42
+ # should validate_numericality_of(:age).only_integer
43
+ # end
44
+ #
45
+ # ##### is_less_than
46
+ #
47
+ # Use `is_less_than` to test usage of the the `:less_than` option. This
48
+ # asserts that the attribute can take a number which is less than the
49
+ # given value and cannot take a number which is greater than or equal to
50
+ # it.
51
+ #
52
+ # class Person
53
+ # include ActiveModel::Model
54
+ #
55
+ # validates_numericality_of :number_of_cars, less_than: 2
56
+ # end
57
+ #
58
+ # # RSpec
59
+ # describe Person do
60
+ # it { should validate_numericality_of(:number_of_cars).is_less_than(2) }
61
+ # end
62
+ #
63
+ # # Test::Unit
64
+ # class PersonTest < ActiveSupport::TestCase
65
+ # should validate_numericality_of(:number_of_cars).is_less_than(2)
66
+ # end
67
+ #
68
+ # ##### is_less_than_or_equal_to
69
+ #
70
+ # Use `is_less_than_or_equal_to` to test usage of the
71
+ # `:less_than_or_equal_to` option. This asserts that the attribute can
72
+ # take a number which is less than or equal to the given value and cannot
73
+ # take a number which is greater than it.
74
+ #
75
+ # class Person
76
+ # include ActiveModel::Model
77
+ #
78
+ # validates_numericality_of :birth_year, less_than_or_equal_to: 1987
79
+ # end
80
+ #
81
+ # # RSpec
82
+ # describe Person do
83
+ # it { should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987) }
84
+ # end
85
+ #
86
+ # # Test::Unit
87
+ # class PersonTest < ActiveSupport::TestCase
88
+ # should validate_numericality_of(:birth_year).is_less_than_or_equal_to(1987)
89
+ # end
90
+ #
91
+ # ##### is_equal_to
92
+ #
93
+ # Use `is_equal_to` to test usage of the `:equal_to` option. This asserts
94
+ # that the attribute can take a number which is equal to the given value
95
+ # and cannot take a number which is not equal.
96
+ #
97
+ # class Person
98
+ # include ActiveModel::Model
99
+ #
100
+ # validates_numericality_of :weight, equal_to: 150
101
+ # end
102
+ #
103
+ # # RSpec
104
+ # describe Person do
105
+ # it { should validate_numericality_of(:weight).is_equal_to(150) }
106
+ # end
107
+ #
108
+ # # Test::Unit
109
+ # class PersonTest < ActiveSupport::TestCase
110
+ # should validate_numericality_of(:weight).is_equal_to(150)
111
+ # end
112
+ #
113
+ # ##### is_greater_than_or_equal_to
114
+ #
115
+ # Use `is_greater_than_or_equal_to` to test usage of the
116
+ # `:greater_than_or_equal_to` option. This asserts that the attribute can
117
+ # take a number which is greater than or equal to the given value and
118
+ # cannot take a number which is less than it.
119
+ #
120
+ # class Person
121
+ # include ActiveModel::Model
122
+ #
123
+ # validates_numericality_of :height, greater_than_or_equal_to: 55
124
+ # end
125
+ #
126
+ # # RSpec
127
+ # describe Person do
128
+ # it { should validate_numericality_of(:height).is_greater_than_or_equal_to(55) }
129
+ # end
130
+ #
131
+ # # Test::Unit
132
+ # class PersonTest < ActiveSupport::TestCase
133
+ # should validate_numericality_of(:height).is_greater_than_or_equal_to(55)
134
+ # end
135
+ #
136
+ # ##### is_greater_than
137
+ #
138
+ # Use `is_greater_than` to test usage of tthe `:greater_than` option.
139
+ # This asserts that the attribute can take a number which is greater than
140
+ # the given value and cannot take a number less than or equal to it.
141
+ #
142
+ # class Person
143
+ # include ActiveModel::Model
144
+ #
145
+ # validates_numericality_of :legal_age, greater_than: 21
146
+ # end
147
+ #
148
+ # # RSpec
149
+ # describe Person do
150
+ # it { should validate_numericality_of(:legal_age).is_greater_than(21) }
151
+ # end
152
+ #
153
+ # # Test::Unit
154
+ # class PersonTest < ActiveSupport::TestCase
155
+ # should validate_numericality_of(:legal_age).is_greater_than(21)
156
+ # end
157
+ #
158
+ # ##### even
159
+ #
160
+ # Use `even` to test usage of the `:even` option. This asserts that the
161
+ # attribute can take odd numbers and cannot take even ones.
162
+ #
163
+ # class Person
164
+ # include ActiveModel::Model
165
+ #
166
+ # validates_numericality_of :birth_month, even: true
167
+ # end
168
+ #
169
+ # # RSpec
170
+ # describe Person do
171
+ # it { should validate_numericality_of(:birth_month).even }
172
+ # end
173
+ #
174
+ # # Test::Unit
175
+ # class PersonTest < ActiveSupport::TestCase
176
+ # should validate_numericality_of(:birth_month).even
177
+ # end
178
+ #
179
+ # ##### odd
180
+ #
181
+ # Use `odd` to test usage of the `:odd` option. This asserts that the
182
+ # attribute can take a number which is odd and cannot take a number which
183
+ # is even.
184
+ #
185
+ # class Person
186
+ # include ActiveModel::Model
187
+ #
188
+ # validates_numericality_of :birth_day, odd: true
189
+ # end
190
+ #
191
+ # # RSpec
192
+ # describe Person do
193
+ # it { should validate_numericality_of(:birth_day).odd }
194
+ # end
195
+ #
196
+ # # Test::Unit
197
+ # class PersonTest < ActiveSupport::TestCase
198
+ # should validate_numericality_of(:birth_day).odd
199
+ # end
200
+ #
201
+ # ##### allow_nil
202
+ #
203
+ # Use `allow_nil` to assert that the attribute allows nil.
204
+ #
205
+ # class Person
206
+ # include ActiveModel::Model
207
+ #
208
+ # validate_numericality_of :rank, allow_nil: true
209
+ # end
210
+ #
211
+ # # RSpec
212
+ # describe Person do
213
+ # it { should validate_numericality_of(:rank).allow_nil }
214
+ # end
215
+ #
216
+ # # Test::Unit
217
+ # class PersonTest < ActiveSupport::TestCase
218
+ # should validate_uniqueness_of(:rank).allow_nil
219
+ # end
220
+ #
221
+ # ##### with_message
222
+ #
223
+ # Use `with_message` if you are using a custom validation message.
224
+ #
225
+ # class Person
226
+ # include ActiveModel::Model
227
+ #
228
+ # validates_numericality_of :number_of_dependents,
229
+ # message: 'Number of dependents must be a number'
230
+ # end
231
+ #
232
+ # # RSpec
233
+ # describe Person do
234
+ # it do
235
+ # should validate_numericality_of(:number_of_dependents).
236
+ # with_message('Number of dependents must be a number')
237
+ # end
238
+ # end
239
+ #
240
+ # # Test::Unit
241
+ # class PersonTest < ActiveSupport::TestCase
242
+ # should validate_numericality_of(:number_of_dependents).
243
+ # with_message('Number of dependents must be a number')
244
+ # end
245
+ #
246
+ # @return [ValidateNumericalityOfMatcher]
247
+ #
248
+ def validate_numericality_of(attr)
249
+ ValidateNumericalityOfMatcher.new(attr)
250
+ end
251
+
252
+ # @private
253
+ class ValidateNumericalityOfMatcher
254
+ NUMERIC_NAME = 'numbers'
255
+ NON_NUMERIC_VALUE = 'abcd'
256
+
257
+ def initialize(attribute)
258
+ @attribute = attribute
259
+ @submatchers = []
260
+
261
+ add_disallow_value_matcher
262
+ end
263
+
264
+ def only_integer
265
+ add_submatcher(NumericalityMatchers::OnlyIntegerMatcher.new(@attribute))
266
+ self
267
+ end
268
+
269
+ def is_greater_than(value)
270
+ add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :>).for(@attribute))
271
+ self
272
+ end
273
+
274
+ def is_greater_than_or_equal_to(value)
275
+ add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :>=).for(@attribute))
276
+ self
277
+ end
278
+
279
+ def is_equal_to(value)
280
+ add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :==).for(@attribute))
281
+ self
282
+ end
283
+
284
+ def is_less_than(value)
285
+ add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :<).for(@attribute))
286
+ self
287
+ end
288
+
289
+ def is_less_than_or_equal_to(value)
290
+ add_submatcher(NumericalityMatchers::ComparisonMatcher.new(value, :<=).for(@attribute))
291
+ self
292
+ end
293
+
294
+ def odd
295
+ odd_number_matcher = NumericalityMatchers::OddNumberMatcher.new(@attribute)
296
+ add_submatcher(odd_number_matcher)
297
+ self
298
+ end
299
+
300
+ def even
301
+ even_number_matcher = NumericalityMatchers::EvenNumberMatcher.new(@attribute)
302
+ add_submatcher(even_number_matcher)
303
+ self
304
+ end
305
+
306
+ def with_message(message)
307
+ @submatchers.each { |matcher| matcher.with_message(message) }
308
+ self
309
+ end
310
+
311
+ def matches?(subject)
312
+ @subject = subject
313
+ submatchers_match?
314
+ end
315
+
316
+ def description
317
+ "only allow #{allowed_types} for #{@attribute}#{comparison_descriptions}"
318
+ end
319
+
320
+ def failure_message
321
+ submatcher_failure_messages_for_should.last
322
+ end
323
+ alias failure_message_for_should failure_message
324
+
325
+ def failure_message_when_negated
326
+ submatcher_failure_messages_for_should_not.last
327
+ end
328
+ alias failure_message_for_should_not failure_message_when_negated
329
+
330
+ private
331
+
332
+ def add_disallow_value_matcher
333
+ disallow_value_matcher = DisallowValueMatcher.new(NON_NUMERIC_VALUE).
334
+ for(@attribute).
335
+ with_message(:not_a_number)
336
+
337
+ add_submatcher(disallow_value_matcher)
338
+ end
339
+
340
+ def add_submatcher(submatcher)
341
+ @submatchers << submatcher
342
+ end
343
+
344
+ def submatchers_match?
345
+ failing_submatchers.empty?
346
+ end
347
+
348
+ def submatcher_failure_messages_for_should
349
+ failing_submatchers.map(&:failure_message)
350
+ end
351
+
352
+ def submatcher_failure_messages_for_should_not
353
+ failing_submatchers.map(&:failure_message_when_negated)
354
+ end
355
+
356
+ def failing_submatchers
357
+ @failing_submatchers ||= @submatchers.select { |matcher| !matcher.matches?(@subject) }
358
+ end
359
+
360
+ def allowed_types
361
+ allowed_array = submatcher_allowed_types
362
+ allowed_array.empty? ? NUMERIC_NAME : allowed_array.join(', ')
363
+ end
364
+
365
+ def submatcher_allowed_types
366
+ @submatchers.inject([]){|m, s| m << s.allowed_type if s.respond_to?(:allowed_type); m }
367
+ end
368
+
369
+ def comparison_descriptions
370
+ description_array = submatcher_comparison_descriptions
371
+ description_array.empty? ? '' : ' which are ' + submatcher_comparison_descriptions.join(' and ')
372
+ end
373
+
374
+ def submatcher_comparison_descriptions
375
+ @submatchers.inject([]){|m, s| m << s.comparison_description if s.respond_to?(:comparison_description); m }
376
+ end
377
+ end
378
+ end
379
+ end
380
+ end