Flamefork-shoulda 2.10.1

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 (160) hide show
  1. data/CONTRIBUTION_GUIDELINES.rdoc +12 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.rdoc +169 -0
  4. data/Rakefile +72 -0
  5. data/bin/convert_to_should_syntax +42 -0
  6. data/lib/shoulda/action_controller/helpers.rb +47 -0
  7. data/lib/shoulda/action_controller/macros.rb +277 -0
  8. data/lib/shoulda/action_controller/matchers/assign_to_matcher.rb +109 -0
  9. data/lib/shoulda/action_controller/matchers/filter_param_matcher.rb +57 -0
  10. data/lib/shoulda/action_controller/matchers/render_with_layout_matcher.rb +81 -0
  11. data/lib/shoulda/action_controller/matchers/respond_with_content_type_matcher.rb +70 -0
  12. data/lib/shoulda/action_controller/matchers/respond_with_matcher.rb +81 -0
  13. data/lib/shoulda/action_controller/matchers/route_matcher.rb +93 -0
  14. data/lib/shoulda/action_controller/matchers/set_session_matcher.rb +87 -0
  15. data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
  16. data/lib/shoulda/action_controller/matchers.rb +37 -0
  17. data/lib/shoulda/action_controller.rb +24 -0
  18. data/lib/shoulda/action_mailer/assertions.rb +38 -0
  19. data/lib/shoulda/action_mailer.rb +6 -0
  20. data/lib/shoulda/action_view/macros.rb +56 -0
  21. data/lib/shoulda/action_view.rb +6 -0
  22. data/lib/shoulda/active_record/assertions.rb +69 -0
  23. data/lib/shoulda/active_record/helpers.rb +40 -0
  24. data/lib/shoulda/active_record/macros.rb +588 -0
  25. data/lib/shoulda/active_record/matchers/allow_mass_assignment_of_matcher.rb +83 -0
  26. data/lib/shoulda/active_record/matchers/allow_value_matcher.rb +102 -0
  27. data/lib/shoulda/active_record/matchers/association_matcher.rb +226 -0
  28. data/lib/shoulda/active_record/matchers/ensure_inclusion_of_matcher.rb +87 -0
  29. data/lib/shoulda/active_record/matchers/ensure_length_of_matcher.rb +141 -0
  30. data/lib/shoulda/active_record/matchers/have_db_column_matcher.rb +169 -0
  31. data/lib/shoulda/active_record/matchers/have_index_matcher.rb +105 -0
  32. data/lib/shoulda/active_record/matchers/have_named_scope_matcher.rb +125 -0
  33. data/lib/shoulda/active_record/matchers/have_readonly_attribute_matcher.rb +59 -0
  34. data/lib/shoulda/active_record/matchers/validate_acceptance_of_matcher.rb +41 -0
  35. data/lib/shoulda/active_record/matchers/validate_numericality_of_matcher.rb +39 -0
  36. data/lib/shoulda/active_record/matchers/validate_presence_of_matcher.rb +60 -0
  37. data/lib/shoulda/active_record/matchers/validate_uniqueness_of_matcher.rb +148 -0
  38. data/lib/shoulda/active_record/matchers/validation_matcher.rb +56 -0
  39. data/lib/shoulda/active_record/matchers.rb +42 -0
  40. data/lib/shoulda/active_record.rb +12 -0
  41. data/lib/shoulda/assertions.rb +59 -0
  42. data/lib/shoulda/autoload_macros.rb +46 -0
  43. data/lib/shoulda/context.rb +304 -0
  44. data/lib/shoulda/helpers.rb +8 -0
  45. data/lib/shoulda/macros.rb +73 -0
  46. data/lib/shoulda/private_helpers.rb +20 -0
  47. data/lib/shoulda/proc_extensions.rb +14 -0
  48. data/lib/shoulda/rails.rb +13 -0
  49. data/lib/shoulda/rspec.rb +11 -0
  50. data/lib/shoulda/tasks/list_tests.rake +29 -0
  51. data/lib/shoulda/tasks/yaml_to_shoulda.rake +28 -0
  52. data/lib/shoulda/tasks.rb +3 -0
  53. data/lib/shoulda/test_unit.rb +14 -0
  54. data/lib/shoulda.rb +9 -0
  55. data/rails/init.rb +7 -0
  56. data/test/README +36 -0
  57. data/test/fail_macros.rb +34 -0
  58. data/test/fixtures/addresses.yml +3 -0
  59. data/test/fixtures/friendships.yml +0 -0
  60. data/test/fixtures/posts.yml +5 -0
  61. data/test/fixtures/products.yml +0 -0
  62. data/test/fixtures/taggings.yml +0 -0
  63. data/test/fixtures/tags.yml +9 -0
  64. data/test/fixtures/users.yml +6 -0
  65. data/test/functional/posts_controller_test.rb +125 -0
  66. data/test/functional/users_controller_test.rb +19 -0
  67. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
  68. data/test/matchers/active_record/allow_value_matcher_test.rb +41 -0
  69. data/test/matchers/active_record/association_matcher_test.rb +258 -0
  70. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
  71. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
  72. data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
  73. data/test/matchers/active_record/have_index_matcher_test.rb +74 -0
  74. data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
  75. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
  76. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
  77. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
  78. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
  79. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
  80. data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
  81. data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
  82. data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
  83. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +27 -0
  84. data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
  85. data/test/matchers/controller/route_matcher_test.rb +58 -0
  86. data/test/matchers/controller/set_session_matcher_test.rb +31 -0
  87. data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
  88. data/test/model_builder.rb +106 -0
  89. data/test/other/autoload_macro_test.rb +18 -0
  90. data/test/other/context_test.rb +145 -0
  91. data/test/other/convert_to_should_syntax_test.rb +63 -0
  92. data/test/other/helpers_test.rb +241 -0
  93. data/test/other/private_helpers_test.rb +34 -0
  94. data/test/other/should_test.rb +266 -0
  95. data/test/rails_root/app/controllers/application.rb +25 -0
  96. data/test/rails_root/app/controllers/posts_controller.rb +86 -0
  97. data/test/rails_root/app/controllers/users_controller.rb +84 -0
  98. data/test/rails_root/app/helpers/application_helper.rb +3 -0
  99. data/test/rails_root/app/helpers/posts_helper.rb +2 -0
  100. data/test/rails_root/app/helpers/users_helper.rb +2 -0
  101. data/test/rails_root/app/models/address.rb +7 -0
  102. data/test/rails_root/app/models/flea.rb +3 -0
  103. data/test/rails_root/app/models/friendship.rb +4 -0
  104. data/test/rails_root/app/models/pets/dog.rb +10 -0
  105. data/test/rails_root/app/models/post.rb +12 -0
  106. data/test/rails_root/app/models/product.rb +12 -0
  107. data/test/rails_root/app/models/tag.rb +8 -0
  108. data/test/rails_root/app/models/tagging.rb +4 -0
  109. data/test/rails_root/app/models/treat.rb +3 -0
  110. data/test/rails_root/app/models/user.rb +29 -0
  111. data/test/rails_root/app/views/layouts/posts.rhtml +19 -0
  112. data/test/rails_root/app/views/layouts/users.rhtml +17 -0
  113. data/test/rails_root/app/views/layouts/wide.html.erb +1 -0
  114. data/test/rails_root/app/views/posts/edit.rhtml +27 -0
  115. data/test/rails_root/app/views/posts/index.rhtml +25 -0
  116. data/test/rails_root/app/views/posts/new.rhtml +26 -0
  117. data/test/rails_root/app/views/posts/show.rhtml +18 -0
  118. data/test/rails_root/app/views/users/edit.rhtml +22 -0
  119. data/test/rails_root/app/views/users/index.rhtml +22 -0
  120. data/test/rails_root/app/views/users/new.rhtml +21 -0
  121. data/test/rails_root/app/views/users/show.rhtml +13 -0
  122. data/test/rails_root/config/boot.rb +109 -0
  123. data/test/rails_root/config/database.yml +4 -0
  124. data/test/rails_root/config/environment.rb +14 -0
  125. data/test/rails_root/config/environments/test.rb +0 -0
  126. data/test/rails_root/config/initializers/new_rails_defaults.rb +15 -0
  127. data/test/rails_root/config/initializers/shoulda.rb +8 -0
  128. data/test/rails_root/config/routes.rb +6 -0
  129. data/test/rails_root/db/migrate/001_create_users.rb +19 -0
  130. data/test/rails_root/db/migrate/002_create_posts.rb +13 -0
  131. data/test/rails_root/db/migrate/003_create_taggings.rb +12 -0
  132. data/test/rails_root/db/migrate/004_create_tags.rb +11 -0
  133. data/test/rails_root/db/migrate/005_create_dogs.rb +12 -0
  134. data/test/rails_root/db/migrate/006_create_addresses.rb +14 -0
  135. data/test/rails_root/db/migrate/007_create_fleas.rb +11 -0
  136. data/test/rails_root/db/migrate/008_create_dogs_fleas.rb +12 -0
  137. data/test/rails_root/db/migrate/009_create_products.rb +17 -0
  138. data/test/rails_root/db/migrate/010_create_friendships.rb +14 -0
  139. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  140. data/test/rails_root/db/schema.rb +0 -0
  141. data/test/rails_root/public/404.html +30 -0
  142. data/test/rails_root/public/422.html +30 -0
  143. data/test/rails_root/public/500.html +30 -0
  144. data/test/rails_root/script/console +3 -0
  145. data/test/rails_root/script/generate +3 -0
  146. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  147. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  148. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  149. data/test/rspec_test.rb +207 -0
  150. data/test/test_helper.rb +35 -0
  151. data/test/unit/address_test.rb +10 -0
  152. data/test/unit/dog_test.rb +10 -0
  153. data/test/unit/flea_test.rb +6 -0
  154. data/test/unit/friendship_test.rb +6 -0
  155. data/test/unit/post_test.rb +19 -0
  156. data/test/unit/product_test.rb +27 -0
  157. data/test/unit/tag_test.rb +15 -0
  158. data/test/unit/tagging_test.rb +6 -0
  159. data/test/unit/user_test.rb +70 -0
  160. metadata +258 -0
@@ -0,0 +1,588 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ # = Macro test helpers for your active record models
4
+ #
5
+ # These helpers will test most of the validations and associations for your ActiveRecord models.
6
+ #
7
+ # class UserTest < Test::Unit::TestCase
8
+ # should_validate_presence_of :name, :phone_number
9
+ # should_not_allow_values_for :phone_number, "abcd", "1234"
10
+ # should_allow_values_for :phone_number, "(123) 456-7890"
11
+ #
12
+ # should_not_allow_mass_assignment_of :password
13
+ #
14
+ # should_have_one :profile
15
+ # should_have_many :dogs
16
+ # should_have_many :messes, :through => :dogs
17
+ # should_belong_to :lover
18
+ # end
19
+ #
20
+ # For all of these helpers, the last parameter may be a hash of options.
21
+ #
22
+ module Macros
23
+ include Helpers
24
+ include Matchers
25
+
26
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
27
+ #
28
+ # If an instance variable has been created in the setup named after the
29
+ # model being tested, then this method will use that. Otherwise, it will
30
+ # create a new instance to test against.
31
+ #
32
+ # Options:
33
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
34
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
35
+ #
36
+ # Example:
37
+ # should_validate_presence_of :name, :phone_number
38
+ #
39
+ def should_validate_presence_of(*attributes)
40
+ message = get_options!(attributes, :message)
41
+ klass = model_class
42
+
43
+ attributes.each do |attribute|
44
+ matcher = validate_presence_of(attribute).with_message(message)
45
+ should matcher.description do
46
+ assert_accepts(matcher, get_instance_of(klass))
47
+ end
48
+ end
49
+ end
50
+
51
+ # Deprecated. See should_validate_presence_of
52
+ def should_require_attributes(*attributes)
53
+ warn "[DEPRECATION] should_require_attributes is deprecated. " <<
54
+ "Use should_validate_presence_of instead."
55
+ should_validate_presence_of(*attributes)
56
+ end
57
+
58
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
59
+ # Requires an existing record
60
+ #
61
+ # Options:
62
+
63
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
64
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
65
+ # * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
66
+ # * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
67
+ # exact match. Ignored by non-text attributes. Default = <tt>true</tt>
68
+ #
69
+ # Examples:
70
+ # should_validate_uniqueness_of :keyword, :username
71
+ # should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
72
+ # should_validate_uniqueness_of :email, :scoped_to => :name
73
+ # should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
74
+ # should_validate_uniqueness_of :email, :case_sensitive => false
75
+ #
76
+ def should_validate_uniqueness_of(*attributes)
77
+ message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
78
+ scope = [*scope].compact
79
+ case_sensitive = true if case_sensitive.nil?
80
+
81
+ klass = model_class
82
+
83
+ attributes.each do |attribute|
84
+ matcher = validate_uniqueness_of(attribute).
85
+ with_message(message).scoped_to(scope)
86
+ matcher = matcher.case_insensitive unless case_sensitive
87
+ should matcher.description do
88
+ assert_accepts(matcher, get_instance_of(klass))
89
+ end
90
+ end
91
+ end
92
+
93
+ # Deprecated. See should_validate_uniqueness_of
94
+ def should_require_unique_attributes(*attributes)
95
+ warn "[DEPRECATION] should_require_unique_attributes is deprecated. " <<
96
+ "Use should_validate_uniqueness_of instead."
97
+ should_validate_uniqueness_of(*attributes)
98
+ end
99
+
100
+ # Ensures that the attribute can be set on mass update.
101
+ #
102
+ # should_allow_mass_assignment_of :first_name, :last_name
103
+ #
104
+ def should_allow_mass_assignment_of(*attributes)
105
+ get_options!(attributes)
106
+ klass = model_class
107
+
108
+ attributes.each do |attribute|
109
+ matcher = allow_mass_assignment_of(attribute)
110
+ should matcher.description do
111
+ assert_accepts matcher, klass.new
112
+ end
113
+ end
114
+ end
115
+
116
+ # Ensures that the attribute cannot be set on mass update.
117
+ #
118
+ # should_not_allow_mass_assignment_of :password, :admin_flag
119
+ #
120
+ def should_not_allow_mass_assignment_of(*attributes)
121
+ get_options!(attributes)
122
+ klass = model_class
123
+
124
+ attributes.each do |attribute|
125
+ matcher = allow_mass_assignment_of(attribute)
126
+ should "not #{matcher.description}" do
127
+ assert_rejects matcher, klass.new
128
+ end
129
+ end
130
+ end
131
+
132
+ # Deprecated. See should_not_allow_mass_assignment_of
133
+ def should_protect_attributes(*attributes)
134
+ warn "[DEPRECATION] should_protect_attributes is deprecated. " <<
135
+ "Use should_not_allow_mass_assignment_of instead."
136
+ should_not_allow_mass_assignment_of(*attributes)
137
+ end
138
+
139
+ # Ensures that the attribute cannot be changed once the record has been created.
140
+ #
141
+ # should_have_readonly_attributes :password, :admin_flag
142
+ #
143
+ def should_have_readonly_attributes(*attributes)
144
+ get_options!(attributes)
145
+ klass = model_class
146
+
147
+ attributes.each do |attribute|
148
+ matcher = have_readonly_attribute(attribute)
149
+ should matcher.description do
150
+ assert_accepts matcher, klass.new
151
+ end
152
+ end
153
+ end
154
+
155
+ # Ensures that the attribute cannot be set to the given values
156
+ #
157
+ # If an instance variable has been created in the setup named after the
158
+ # model being tested, then this method will use that. Otherwise, it will
159
+ # create a new instance to test against.
160
+ #
161
+ # Options:
162
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
163
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
164
+ #
165
+ # Example:
166
+ # should_not_allow_values_for :isbn, "bad 1", "bad 2"
167
+ #
168
+ def should_not_allow_values_for(attribute, *bad_values)
169
+ message = get_options!(bad_values, :message)
170
+ klass = model_class
171
+ bad_values.each do |value|
172
+ matcher = allow_value(value).for(attribute).with_message(message)
173
+ should "not #{matcher.description}" do
174
+ assert_rejects matcher, get_instance_of(klass)
175
+ end
176
+ end
177
+ end
178
+
179
+ # Ensures that the attribute can be set to the given values.
180
+ #
181
+ # If an instance variable has been created in the setup named after the
182
+ # model being tested, then this method will use that. Otherwise, it will
183
+ # create a new instance to test against.
184
+ #
185
+ # Example:
186
+ # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
187
+ #
188
+ def should_allow_values_for(attribute, *good_values)
189
+ get_options!(good_values)
190
+ klass = model_class
191
+ good_values.each do |value|
192
+ matcher = allow_value(value).for(attribute)
193
+ should matcher.description do
194
+ assert_accepts matcher, get_instance_of(klass)
195
+ end
196
+ end
197
+ end
198
+
199
+ # Ensures that the length of the attribute is in the given range
200
+ #
201
+ # If an instance variable has been created in the setup named after the
202
+ # model being tested, then this method will use that. Otherwise, it will
203
+ # create a new instance to test against.
204
+ #
205
+ # Options:
206
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
207
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
208
+ # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
209
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
210
+ #
211
+ # Example:
212
+ # should_ensure_length_in_range :password, (6..20)
213
+ #
214
+ def should_ensure_length_in_range(attribute, range, opts = {})
215
+ short_message, long_message = get_options!([opts],
216
+ :short_message,
217
+ :long_message)
218
+ klass = model_class
219
+
220
+ matcher = ensure_length_of(attribute).
221
+ is_at_least(range.first).
222
+ with_short_message(short_message).
223
+ is_at_most(range.last).
224
+ with_long_message(long_message)
225
+
226
+ should matcher.description do
227
+ assert_accepts matcher, get_instance_of(klass)
228
+ end
229
+ end
230
+
231
+ # Ensures that the length of the attribute is at least a certain length
232
+ #
233
+ # If an instance variable has been created in the setup named after the
234
+ # model being tested, then this method will use that. Otherwise, it will
235
+ # create a new instance to test against.
236
+ #
237
+ # Options:
238
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
239
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
240
+ #
241
+ # Example:
242
+ # should_ensure_length_at_least :name, 3
243
+ #
244
+ def should_ensure_length_at_least(attribute, min_length, opts = {})
245
+ short_message = get_options!([opts], :short_message)
246
+ klass = model_class
247
+
248
+ matcher = ensure_length_of(attribute).
249
+ is_at_least(min_length).
250
+ with_short_message(short_message)
251
+
252
+ should matcher.description do
253
+ assert_accepts matcher, get_instance_of(klass)
254
+ end
255
+ end
256
+
257
+ # Ensures that the length of the attribute is exactly a certain length
258
+ #
259
+ # If an instance variable has been created in the setup named after the
260
+ # model being tested, then this method will use that. Otherwise, it will
261
+ # create a new instance to test against.
262
+ #
263
+ # Options:
264
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
265
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
266
+ #
267
+ # Example:
268
+ # should_ensure_length_is :ssn, 9
269
+ #
270
+ def should_ensure_length_is(attribute, length, opts = {})
271
+ message = get_options!([opts], :message)
272
+ klass = model_class
273
+ matcher = ensure_length_of(attribute).
274
+ is_equal_to(length).
275
+ with_message(message)
276
+
277
+ should matcher.description do
278
+ assert_accepts matcher, get_instance_of(klass)
279
+ end
280
+ end
281
+
282
+ # Ensure that the attribute is in the range specified
283
+ #
284
+ # If an instance variable has been created in the setup named after the
285
+ # model being tested, then this method will use that. Otherwise, it will
286
+ # create a new instance to test against.
287
+ #
288
+ # Options:
289
+ # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
290
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
291
+ # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
292
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
293
+ #
294
+ # Example:
295
+ # should_ensure_value_in_range :age, (0..100)
296
+ #
297
+ def should_ensure_value_in_range(attribute, range, opts = {})
298
+ message, low_message, high_message = get_options!([opts],
299
+ :message,
300
+ :low_message,
301
+ :high_message)
302
+ klass = model_class
303
+ matcher = ensure_inclusion_of(attribute).
304
+ in_range(range).
305
+ with_message(message).
306
+ with_low_message(low_message).
307
+ with_high_message(high_message)
308
+ should matcher.description do
309
+ assert_accepts matcher, get_instance_of(klass)
310
+ end
311
+ end
312
+
313
+ # Ensure that the attribute is numeric
314
+ #
315
+ # If an instance variable has been created in the setup named after the
316
+ # model being tested, then this method will use that. Otherwise, it will
317
+ # create a new instance to test against.
318
+ #
319
+ # Options:
320
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
321
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
322
+ #
323
+ # Example:
324
+ # should_validate_numericality_of :age
325
+ #
326
+ def should_validate_numericality_of(*attributes)
327
+ message = get_options!(attributes, :message)
328
+ klass = model_class
329
+ attributes.each do |attribute|
330
+ matcher = validate_numericality_of(attribute).
331
+ with_message(message)
332
+ should matcher.description do
333
+ assert_accepts matcher, get_instance_of(klass)
334
+ end
335
+ end
336
+ end
337
+
338
+ # Deprecated. See should_validate_numericality_of
339
+ def should_only_allow_numeric_values_for(*attributes)
340
+ warn "[DEPRECATION] should_only_allow_numeric_values_for is " <<
341
+ "deprecated. Use should_validate_numericality_of instead."
342
+ should_validate_numericality_of(*attributes)
343
+ end
344
+
345
+ # Ensures that the has_many relationship exists. Will also test that the
346
+ # associated table has the required columns. Works with polymorphic
347
+ # associations.
348
+ #
349
+ # Options:
350
+ # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
351
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
352
+ #
353
+ # Example:
354
+ # should_have_many :friends
355
+ # should_have_many :enemies, :through => :friends
356
+ # should_have_many :enemies, :dependent => :destroy
357
+ #
358
+ def should_have_many(*associations)
359
+ through, dependent = get_options!(associations, :through, :dependent)
360
+ klass = model_class
361
+ associations.each do |association|
362
+ matcher = have_many(association).through(through).dependent(dependent)
363
+ should matcher.description do
364
+ assert_accepts(matcher, klass.new)
365
+ end
366
+ end
367
+ end
368
+
369
+ # Ensure that the has_one relationship exists. Will also test that the
370
+ # associated table has the required columns. Works with polymorphic
371
+ # associations.
372
+ #
373
+ # Options:
374
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
375
+ #
376
+ # Example:
377
+ # should_have_one :god # unless hindu
378
+ #
379
+ def should_have_one(*associations)
380
+ dependent = get_options!(associations, :dependent)
381
+ klass = model_class
382
+ associations.each do |association|
383
+ matcher = have_one(association).dependent(dependent)
384
+ should matcher.description do
385
+ assert_accepts(matcher, klass.new)
386
+ end
387
+ end
388
+ end
389
+
390
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join
391
+ # table is in place.
392
+ #
393
+ # should_have_and_belong_to_many :posts, :cars
394
+ #
395
+ def should_have_and_belong_to_many(*associations)
396
+ get_options!(associations)
397
+ klass = model_class
398
+
399
+ associations.each do |association|
400
+ matcher = have_and_belong_to_many(association)
401
+ should matcher.description do
402
+ assert_accepts(matcher, klass.new)
403
+ end
404
+ end
405
+ end
406
+
407
+ # Ensure that the belongs_to relationship exists.
408
+ #
409
+ # should_belong_to :parent
410
+ #
411
+ def should_belong_to(*associations)
412
+ dependent = get_options!(associations, :dependent)
413
+ klass = model_class
414
+ associations.each do |association|
415
+ matcher = belong_to(association).dependent(dependent)
416
+ should matcher.description do
417
+ assert_accepts(matcher, klass.new)
418
+ end
419
+ end
420
+ end
421
+
422
+ # Ensure that the given class methods are defined on the model.
423
+ #
424
+ # should_have_class_methods :find, :destroy
425
+ #
426
+ def should_have_class_methods(*methods)
427
+ get_options!(methods)
428
+ klass = model_class
429
+ methods.each do |method|
430
+ should "respond to class method ##{method}" do
431
+ assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
432
+ end
433
+ end
434
+ end
435
+
436
+ # Ensure that the given instance methods are defined on the model.
437
+ #
438
+ # should_have_instance_methods :email, :name, :name=
439
+ #
440
+ def should_have_instance_methods(*methods)
441
+ get_options!(methods)
442
+ klass = model_class
443
+ methods.each do |method|
444
+ should "respond to instance method ##{method}" do
445
+ assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
446
+ end
447
+ end
448
+ end
449
+
450
+ # Ensure that the given columns are defined on the models backing SQL table.
451
+ # Also aliased to should_have_index for readability.
452
+ # Takes the same options available in migrations:
453
+ # :type, :precision, :limit, :default, :null, and :scale
454
+ #
455
+ # Examples:
456
+ #
457
+ # should_have_db_columns :id, :email, :name, :created_at
458
+ #
459
+ # should_have_db_column :email, :type => "string", :limit => 255
460
+ # should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
461
+ # should_have_db_column :admin, :default => false, :null => false
462
+ #
463
+ def should_have_db_columns(*columns)
464
+ column_type, precision, limit, default, null, scale, sql_type =
465
+ get_options!(columns, :type, :precision, :limit,
466
+ :default, :null, :scale, :sql_type)
467
+ klass = model_class
468
+ columns.each do |name|
469
+ matcher = have_db_column(name).
470
+ of_type(column_type).
471
+ with_options(:precision => precision, :limit => limit,
472
+ :default => default, :null => null,
473
+ :scale => scale, :sql_type => sql_type)
474
+ should matcher.description do
475
+ assert_accepts(matcher, klass.new)
476
+ end
477
+ end
478
+ end
479
+
480
+ alias_method :should_have_db_column, :should_have_db_columns
481
+
482
+ # Ensures that there are DB indices on the given columns or tuples of columns.
483
+ # Also aliased to should_have_index for readability
484
+ #
485
+ # Options:
486
+ # * <tt>:unique</tt> - whether or not the index has a unique
487
+ # constraint. Use <tt>true</tt> to explicitly test for a unique
488
+ # constraint. Use <tt>false</tt> to explicitly test for a non-unique
489
+ # constraint. Use <tt>nil</tt> if you don't care whether the index is
490
+ # unique or not. Default = <tt>nil</tt>
491
+ #
492
+ # Examples:
493
+ #
494
+ # should_have_indices :email, :name, [:commentable_type, :commentable_id]
495
+ # should_have_index :age
496
+ # should_have_index :ssn, :unique => true
497
+ #
498
+ def should_have_indices(*columns)
499
+ unique = get_options!(columns, :unique)
500
+ klass = model_class
501
+
502
+ columns.each do |column|
503
+ matcher = have_index(column).unique(unique)
504
+ should matcher.description do
505
+ assert_accepts(matcher, klass.new)
506
+ end
507
+ end
508
+ end
509
+
510
+ alias_method :should_have_index, :should_have_indices
511
+
512
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
513
+ #
514
+ # If an instance variable has been created in the setup named after the
515
+ # model being tested, then this method will use that. Otherwise, it will
516
+ # create a new instance to test against.
517
+ #
518
+ # Options:
519
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
520
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
521
+ #
522
+ # Example:
523
+ # should_validate_acceptance_of :eula
524
+ #
525
+ def should_validate_acceptance_of(*attributes)
526
+ message = get_options!(attributes, :message)
527
+ klass = model_class
528
+
529
+ attributes.each do |attribute|
530
+ matcher = validate_acceptance_of(attribute).with_message(message)
531
+ should matcher.description do
532
+ assert_accepts matcher, get_instance_of(klass)
533
+ end
534
+ end
535
+ end
536
+
537
+ # Deprecated. See should_validate_uniqueness_of
538
+ def should_require_acceptance_of(*attributes)
539
+ warn "[DEPRECATION] should_require_acceptance_of is deprecated. " <<
540
+ "Use should_validate_acceptance_of instead."
541
+ should_validate_acceptance_of(*attributes)
542
+ end
543
+
544
+ # Ensures that the model has a method named scope_name that returns a NamedScope object with the
545
+ # proxy options set to the options you supply. scope_name can be either a symbol, or a method
546
+ # call which will be evaled against the model. The eval'd method call has access to all the same
547
+ # instance variables that a should statement would.
548
+ #
549
+ # Options: Any of the options that the named scope would pass on to find.
550
+ #
551
+ # Example:
552
+ #
553
+ # should_have_named_scope :visible, :conditions => {:visible => true}
554
+ #
555
+ # Passes for
556
+ #
557
+ # named_scope :visible, :conditions => {:visible => true}
558
+ #
559
+ # Or for
560
+ #
561
+ # def self.visible
562
+ # scoped(:conditions => {:visible => true})
563
+ # end
564
+ #
565
+ # You can test lambdas or methods that return ActiveRecord#scoped calls:
566
+ #
567
+ # should_have_named_scope 'recent(5)', :limit => 5
568
+ # should_have_named_scope 'recent(1)', :limit => 1
569
+ #
570
+ # Passes for
571
+ # named_scope :recent, lambda {|c| {:limit => c}}
572
+ #
573
+ # Or for
574
+ #
575
+ # def self.recent(c)
576
+ # scoped(:limit => c)
577
+ # end
578
+ #
579
+ def should_have_named_scope(scope_call, find_options = nil)
580
+ klass = model_class
581
+ matcher = have_named_scope(scope_call).finding(find_options)
582
+ should matcher.description do
583
+ assert_accepts matcher.in_context(self), klass.new
584
+ end
585
+ end
586
+ end
587
+ end
588
+ end
@@ -0,0 +1,83 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers
4
+
5
+ # Ensures that the attribute can be set on mass update.
6
+ #
7
+ # it { should_not allow_mass_assignment_of(:password) }
8
+ # it { should allow_mass_assignment_of(:first_name) }
9
+ #
10
+ def allow_mass_assignment_of(value)
11
+ AllowMassAssignmentOfMatcher.new(value)
12
+ end
13
+
14
+ class AllowMassAssignmentOfMatcher # :nodoc:
15
+
16
+ def initialize(attribute)
17
+ @attribute = attribute.to_s
18
+ end
19
+
20
+ def matches?(subject)
21
+ @subject = subject
22
+ if attr_mass_assignable?
23
+ if whitelisting?
24
+ @failure_message = "#{@attribute} was made accessible"
25
+ else
26
+ if protected_attributes.empty?
27
+ @failure_message = "no attributes were protected"
28
+ else
29
+ @failure_message = "#{class_name} is protecting " <<
30
+ "#{protected_attributes.to_a.to_sentence}, " <<
31
+ "but not #{@attribute}."
32
+ end
33
+ end
34
+ true
35
+ else
36
+ if whitelisting?
37
+ @negative_failure_message =
38
+ "Expected #{@attribute} to be accessible"
39
+ else
40
+ @negative_failure_message =
41
+ "Did not expect #{@attribute} to be protected"
42
+ end
43
+ false
44
+ end
45
+ end
46
+
47
+ attr_reader :failure_message, :negative_failure_message
48
+
49
+ def description
50
+ "allow mass assignment of #{@attribute}"
51
+ end
52
+
53
+ private
54
+
55
+ def protected_attributes
56
+ @protected_attributes ||= (@subject.class.protected_attributes || [])
57
+ end
58
+
59
+ def accessible_attributes
60
+ @accessible_attributes ||= (@subject.class.accessible_attributes || [])
61
+ end
62
+
63
+ def whitelisting?
64
+ !accessible_attributes.empty?
65
+ end
66
+
67
+ def attr_mass_assignable?
68
+ if whitelisting?
69
+ accessible_attributes.include?(@attribute)
70
+ else
71
+ !protected_attributes.include?(@attribute)
72
+ end
73
+ end
74
+
75
+ def class_name
76
+ @subject.class.name
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end
83
+ end