robert-shoulda 2.10.3

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