technicalpickles-shoulda 2.0.6 → 2.10.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 (108) hide show
  1. data/README.rdoc +61 -11
  2. data/Rakefile +7 -5
  3. data/lib/shoulda.rb +7 -15
  4. data/lib/shoulda/action_controller.rb +28 -0
  5. data/lib/shoulda/action_controller/helpers.rb +47 -0
  6. data/lib/shoulda/action_controller/macros.rb +277 -0
  7. data/lib/shoulda/action_controller/matchers.rb +37 -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 +83 -0
  15. data/lib/shoulda/action_controller/matchers/set_the_flash_matcher.rb +85 -0
  16. data/lib/shoulda/action_mailer.rb +1 -1
  17. data/lib/shoulda/action_mailer/assertions.rb +32 -33
  18. data/lib/shoulda/action_view.rb +10 -0
  19. data/lib/shoulda/action_view/macros.rb +56 -0
  20. data/lib/shoulda/active_record.rb +6 -2
  21. data/lib/shoulda/active_record/assertions.rb +62 -81
  22. data/lib/shoulda/active_record/helpers.rb +40 -0
  23. data/lib/shoulda/active_record/macros.rb +514 -640
  24. data/lib/shoulda/active_record/matchers.rb +42 -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/assertions.rb +50 -40
  40. data/lib/shoulda/autoload_macros.rb +46 -0
  41. data/lib/shoulda/context.rb +124 -126
  42. data/lib/shoulda/helpers.rb +5 -7
  43. data/lib/shoulda/macros.rb +63 -64
  44. data/lib/shoulda/private_helpers.rb +16 -18
  45. data/lib/shoulda/rails.rb +5 -11
  46. data/lib/shoulda/rspec.rb +11 -0
  47. data/lib/shoulda/tasks/list_tests.rake +6 -1
  48. data/lib/shoulda/test_unit.rb +19 -0
  49. data/rails/init.rb +7 -1
  50. data/test/README +2 -2
  51. data/test/fail_macros.rb +16 -16
  52. data/test/functional/posts_controller_test.rb +33 -24
  53. data/test/functional/users_controller_test.rb +0 -19
  54. data/test/matchers/active_record/allow_mass_assignment_of_matcher_test.rb +68 -0
  55. data/test/matchers/active_record/allow_value_matcher_test.rb +41 -0
  56. data/test/matchers/active_record/association_matcher_test.rb +258 -0
  57. data/test/matchers/active_record/ensure_inclusion_of_matcher_test.rb +80 -0
  58. data/test/matchers/active_record/ensure_length_of_matcher_test.rb +158 -0
  59. data/test/matchers/active_record/have_db_column_matcher_test.rb +169 -0
  60. data/test/matchers/active_record/have_index_matcher_test.rb +74 -0
  61. data/test/matchers/active_record/have_named_scope_matcher_test.rb +65 -0
  62. data/test/matchers/active_record/have_readonly_attributes_matcher_test.rb +29 -0
  63. data/test/matchers/active_record/validate_acceptance_of_matcher_test.rb +44 -0
  64. data/test/matchers/active_record/validate_numericality_of_matcher_test.rb +52 -0
  65. data/test/matchers/active_record/validate_presence_of_matcher_test.rb +86 -0
  66. data/test/matchers/active_record/validate_uniqueness_of_matcher_test.rb +147 -0
  67. data/test/matchers/controller/assign_to_matcher_test.rb +35 -0
  68. data/test/matchers/controller/filter_param_matcher_test.rb +32 -0
  69. data/test/matchers/controller/render_with_layout_matcher_test.rb +33 -0
  70. data/test/matchers/controller/respond_with_content_type_matcher_test.rb +27 -0
  71. data/test/matchers/controller/respond_with_matcher_test.rb +106 -0
  72. data/test/matchers/controller/route_matcher_test.rb +58 -0
  73. data/test/matchers/controller/set_session_matcher_test.rb +27 -0
  74. data/test/matchers/controller/set_the_flash_matcher.rb +41 -0
  75. data/test/model_builder.rb +106 -0
  76. data/test/other/autoload_macro_test.rb +18 -0
  77. data/test/other/helpers_test.rb +58 -0
  78. data/test/other/private_helpers_test.rb +1 -1
  79. data/test/other/should_test.rb +16 -16
  80. data/test/rails_root/app/controllers/posts_controller.rb +6 -5
  81. data/test/rails_root/app/models/pets/dog.rb +10 -0
  82. data/test/rails_root/app/models/treat.rb +3 -0
  83. data/test/rails_root/app/models/user.rb +4 -3
  84. data/test/rails_root/app/views/layouts/posts.rhtml +2 -0
  85. data/test/rails_root/config/database.yml +1 -1
  86. data/test/rails_root/config/environment.rb +1 -1
  87. data/test/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  88. data/test/rails_root/db/migrate/001_create_users.rb +3 -2
  89. data/test/rails_root/db/migrate/011_create_treats.rb +12 -0
  90. data/test/rails_root/test/shoulda_macros/custom_macro.rb +6 -0
  91. data/test/rails_root/vendor/gems/gem_with_macro-0.0.1/shoulda_macros/gem_macro.rb +6 -0
  92. data/test/rails_root/vendor/plugins/plugin_with_macro/shoulda_macros/plugin_macro.rb +6 -0
  93. data/test/rspec_test.rb +207 -0
  94. data/test/test_helper.rb +3 -1
  95. data/test/unit/address_test.rb +1 -1
  96. data/test/unit/dog_test.rb +5 -2
  97. data/test/unit/post_test.rb +7 -3
  98. data/test/unit/product_test.rb +2 -2
  99. data/test/unit/tag_test.rb +2 -1
  100. data/test/unit/user_test.rb +27 -9
  101. metadata +82 -13
  102. data/lib/shoulda/controller.rb +0 -30
  103. data/lib/shoulda/controller/formats/html.rb +0 -201
  104. data/lib/shoulda/controller/formats/xml.rb +0 -170
  105. data/lib/shoulda/controller/helpers.rb +0 -64
  106. data/lib/shoulda/controller/macros.rb +0 -316
  107. data/lib/shoulda/controller/resource_options.rb +0 -236
  108. data/test/rails_root/app/models/dog.rb +0 -5
@@ -0,0 +1,40 @@
1
+ module Shoulda # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Helpers
4
+ def pretty_error_messages(obj) # :nodoc:
5
+ obj.errors.map do |a, m|
6
+ msg = "#{a} #{m}"
7
+ msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
8
+ end
9
+ end
10
+
11
+ def get_instance_of(object_or_klass)
12
+ if object_or_klass.is_a?(Class)
13
+ klass = object_or_klass
14
+ instance_variable_get("@#{instance_variable_name_for(klass)}") || klass.new
15
+ else
16
+ object_or_klass
17
+ end
18
+ end
19
+
20
+ def instance_variable_name_for(klass)
21
+ klass.to_s.split('::').last.underscore
22
+ end
23
+
24
+ # Helper method that determines the default error message used by Active
25
+ # Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
26
+ # introduced I18n module used for localization.
27
+ #
28
+ # default_error_message(:blank)
29
+ # default_error_message(:too_short, :count => 5)
30
+ # default_error_message(:too_long, :count => 60)
31
+ def default_error_message(key, values = {})
32
+ if Object.const_defined?(:I18n) # Rails >= 2.2
33
+ I18n.translate("activerecord.errors.messages.#{key}", values)
34
+ else # Rails <= 2.1.x
35
+ ::ActiveRecord::Errors.default_error_messages[key] % values[:count]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,713 +1,587 @@
1
- module ThoughtBot # :nodoc:
2
- module Shoulda # :nodoc:
3
- module ActiveRecord # :nodoc:
4
- module MacroHelpers # :nodoc:
5
- # Helper method that determines the default error message used by Active
6
- # Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
7
- # introduced I18n module used for localization.
8
- #
9
- # default_error_message(:blank)
10
- # default_error_message(:too_short, :count => 5)
11
- # default_error_message(:too_long, :count => 60)
12
- def default_error_message(key, values = {})
13
- if Object.const_defined?(:I18n) # Rails >= 2.2
14
- I18n.translate("activerecord.errors.messages.#{key}", values)
15
- else # Rails <= 2.1.x
16
- ::ActiveRecord::Errors.default_error_messages[key] % values[:count]
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))
17
47
  end
18
48
  end
19
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
20
57
 
21
- # = Macro test helpers for your active record models
22
- #
23
- # These helpers will test most of the validations and associations for your ActiveRecord models.
24
- #
25
- # class UserTest < Test::Unit::TestCase
26
- # should_require_attributes :name, :phone_number
27
- # should_not_allow_values_for :phone_number, "abcd", "1234"
28
- # should_allow_values_for :phone_number, "(123) 456-7890"
29
- #
30
- # should_protect_attributes :password
58
+ # Ensures that the model cannot be saved if one of the attributes listed is not unique.
59
+ # Requires an existing record
31
60
  #
32
- # should_have_one :profile
33
- # should_have_many :dogs
34
- # should_have_many :messes, :through => :dogs
35
- # should_belong_to :lover
36
- # end
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>
37
68
  #
38
- # For all of these helpers, the last parameter may be a hash of options.
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
39
75
  #
40
- module Macros
41
- include MacroHelpers
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?
42
80
 
43
- # <b>DEPRECATED:</b> Use <tt>fixtures :all</tt> instead
44
- #
45
- # Loads all fixture files (<tt>test/fixtures/*.yml</tt>)
46
- def load_all_fixtures
47
- warn "[DEPRECATION] load_all_fixtures is deprecated. Use `fixtures :all` instead."
48
- fixtures :all
49
- end
81
+ klass = model_class
50
82
 
51
- # Ensures that the model cannot be saved if one of the attributes listed is not present.
52
- #
53
- # If an instance variable has been created in the setup named after the
54
- # model being tested, then this method will use that. Otherwise, it will
55
- # create a new instance to test against.
56
- #
57
- # Options:
58
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
59
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
60
- #
61
- # Example:
62
- # should_require_attributes :name, :phone_number
63
- #
64
- def should_require_attributes(*attributes)
65
- message = get_options!(attributes, :message)
66
- message ||= default_error_message(:blank)
67
- klass = model_class
68
-
69
- attributes.each do |attribute|
70
- should "require #{attribute} to be set" do
71
- assert_bad_value(klass, attribute, nil, message)
72
- end
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))
73
89
  end
74
90
  end
91
+ end
75
92
 
76
- # Ensures that the model cannot be saved if one of the attributes listed is not unique.
77
- # Requires an existing record
78
- #
79
- # Options:
80
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
81
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
82
- # * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
83
- #
84
- # Examples:
85
- # should_require_unique_attributes :keyword, :username
86
- # should_require_unique_attributes :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
87
- # should_require_unique_attributes :email, :scoped_to => :name
88
- # should_require_unique_attributes :address, :scoped_to => [:first_name, :last_name]
89
- #
90
- def should_require_unique_attributes(*attributes)
91
- message, scope = get_options!(attributes, :message, :scoped_to)
92
- scope = [*scope].compact
93
- message ||= default_error_message(:taken)
94
-
95
- klass = model_class
96
- attributes.each do |attribute|
97
- attribute = attribute.to_sym
98
- should "require unique value for #{attribute}#{" scoped to #{scope.join(', ')}" unless scope.blank?}" do
99
- assert existing = klass.find(:first), "Can't find first #{klass}"
100
- object = klass.new
101
- existing_value = existing.send(attribute)
102
-
103
- if !scope.blank?
104
- scope.each do |s|
105
- assert_respond_to object, :"#{s}=", "#{klass.name} doesn't seem to have a #{s} attribute."
106
- object.send("#{s}=", existing.send(s))
107
- end
108
- end
109
- assert_bad_value(object, attribute, existing_value, message)
110
-
111
- # Now test that the object is valid when changing the scoped attribute
112
- # TODO: There is a chance that we could change the scoped field
113
- # to a value that's already taken. An alternative implementation
114
- # could actually find all values for scope and create a unique
115
- # one.
116
- if !scope.blank?
117
- scope.each do |s|
118
- # Assume the scope is a foreign key if the field is nil
119
- object.send("#{s}=", existing.send(s).nil? ? 1 : existing.send(s).next)
120
- assert_good_value(object, attribute, existing_value, message)
121
- end
122
- end
123
- end
124
- end
125
- end
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
126
99
 
127
- # Ensures that the attribute cannot be set on mass update.
128
- #
129
- # should_protect_attributes :password, :admin_flag
130
- #
131
- def should_protect_attributes(*attributes)
132
- get_options!(attributes)
133
- klass = model_class
134
-
135
- attributes.each do |attribute|
136
- attribute = attribute.to_sym
137
- should "protect #{attribute} from mass updates" do
138
- protected = klass.protected_attributes || []
139
- accessible = klass.accessible_attributes || []
140
-
141
- assert protected.include?(attribute.to_s) ||
142
- (!accessible.empty? && !accessible.include?(attribute.to_s)),
143
- (accessible.empty? ?
144
- "#{klass} is protecting #{protected.to_a.to_sentence}, but not #{attribute}." :
145
- "#{klass} has made #{attribute} accessible")
146
- end
147
- end
148
- end
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
149
107
 
150
- # Ensures that the attribute cannot be changed once the record has been created.
151
- #
152
- # should_have_readonly_attributes :password, :admin_flag
153
- #
154
- def should_have_readonly_attributes(*attributes)
155
- get_options!(attributes)
156
- klass = model_class
157
-
158
- attributes.each do |attribute|
159
- attribute = attribute.to_sym
160
- should "make #{attribute} read-only" do
161
- readonly = klass.readonly_attributes || []
162
-
163
- assert readonly.include?(attribute.to_s),
164
- (readonly.empty? ?
165
- "#{klass} attribute #{attribute} is not read-only" :
166
- "#{klass} is making #{readonly.to_a.to_sentence} read-only, but not #{attribute}.")
167
- end
108
+ attributes.each do |attribute|
109
+ matcher = allow_mass_assignment_of(attribute)
110
+ should matcher.description do
111
+ assert_accepts matcher, klass.new
168
112
  end
169
113
  end
114
+ end
170
115
 
171
- # Ensures that the attribute cannot be set to the given values
172
- #
173
- # If an instance variable has been created in the setup named after the
174
- # model being tested, then this method will use that. Otherwise, it will
175
- # create a new instance to test against.
176
- #
177
- # Options:
178
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
179
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
180
- #
181
- # Example:
182
- # should_not_allow_values_for :isbn, "bad 1", "bad 2"
183
- #
184
- def should_not_allow_values_for(attribute, *bad_values)
185
- message = get_options!(bad_values, :message)
186
- message ||= default_error_message(:invalid)
187
- klass = model_class
188
- bad_values.each do |v|
189
- should "not allow #{attribute} to be set to #{v.inspect}" do
190
- assert_bad_value(klass, attribute, v, message)
191
- end
192
- end
193
- end
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
194
123
 
195
- # Ensures that the attribute can be set to the given values.
196
- #
197
- # If an instance variable has been created in the setup named after the
198
- # model being tested, then this method will use that. Otherwise, it will
199
- # create a new instance to test against.
200
- #
201
- # Example:
202
- # should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
203
- #
204
- def should_allow_values_for(attribute, *good_values)
205
- get_options!(good_values)
206
- klass = model_class
207
- good_values.each do |v|
208
- should "allow #{attribute} to be set to #{v.inspect}" do
209
- assert_good_value(klass, attribute, v)
210
- end
124
+ attributes.each do |attribute|
125
+ matcher = allow_mass_assignment_of(attribute)
126
+ should "not #{matcher.description}" do
127
+ assert_rejects matcher, klass.new
211
128
  end
212
129
  end
130
+ end
213
131
 
214
- # Ensures that the length of the attribute is in the given range
215
- #
216
- # If an instance variable has been created in the setup named after the
217
- # model being tested, then this method will use that. Otherwise, it will
218
- # create a new instance to test against.
219
- #
220
- # Options:
221
- # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
222
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
223
- # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
224
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
225
- #
226
- # Example:
227
- # should_ensure_length_in_range :password, (6..20)
228
- #
229
- def should_ensure_length_in_range(attribute, range, opts = {})
230
- short_message, long_message = get_options!([opts], :short_message, :long_message)
231
- short_message ||= default_error_message(:too_short, :count => range.first)
232
- long_message ||= default_error_message(:too_long, :count => range.last)
233
-
234
- klass = model_class
235
- min_length = range.first
236
- max_length = range.last
237
- same_length = (min_length == max_length)
238
-
239
- if min_length > 0
240
- should "not allow #{attribute} to be less than #{min_length} chars long" do
241
- min_value = "x" * (min_length - 1)
242
- assert_bad_value(klass, attribute, min_value, short_message)
243
- end
244
- end
245
-
246
- if min_length > 0
247
- should "allow #{attribute} to be exactly #{min_length} chars long" do
248
- min_value = "x" * min_length
249
- assert_good_value(klass, attribute, min_value, short_message)
250
- end
251
- end
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
252
138
 
253
- should "not allow #{attribute} to be more than #{max_length} chars long" do
254
- max_value = "x" * (max_length + 1)
255
- assert_bad_value(klass, attribute, max_value, long_message)
256
- end
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
257
146
 
258
- unless same_length
259
- should "allow #{attribute} to be exactly #{max_length} chars long" do
260
- max_value = "x" * max_length
261
- assert_good_value(klass, attribute, max_value, long_message)
262
- end
147
+ attributes.each do |attribute|
148
+ matcher = have_readonly_attribute(attribute)
149
+ should matcher.description do
150
+ assert_accepts matcher, klass.new
263
151
  end
264
152
  end
153
+ end
265
154
 
266
- # Ensures that the length of the attribute is at least a certain length
267
- #
268
- # If an instance variable has been created in the setup named after the
269
- # model being tested, then this method will use that. Otherwise, it will
270
- # create a new instance to test against.
271
- #
272
- # Options:
273
- # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
274
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
275
- #
276
- # Example:
277
- # should_ensure_length_at_least :name, 3
278
- #
279
- def should_ensure_length_at_least(attribute, min_length, opts = {})
280
- short_message = get_options!([opts], :short_message)
281
- short_message ||= default_error_message(:too_short, :count => min_length)
282
-
283
- klass = model_class
284
-
285
- if min_length > 0
286
- min_value = "x" * (min_length - 1)
287
- should "not allow #{attribute} to be less than #{min_length} chars long" do
288
- assert_bad_value(klass, attribute, min_value, short_message)
289
- end
290
- end
291
- should "allow #{attribute} to be at least #{min_length} chars long" do
292
- valid_value = "x" * (min_length)
293
- assert_good_value(klass, attribute, valid_value, short_message)
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)
294
175
  end
295
176
  end
177
+ end
296
178
 
297
- # Ensures that the length of the attribute is exactly a certain length
298
- #
299
- # If an instance variable has been created in the setup named after the
300
- # model being tested, then this method will use that. Otherwise, it will
301
- # create a new instance to test against.
302
- #
303
- # Options:
304
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
305
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
306
- #
307
- # Example:
308
- # should_ensure_length_is :ssn, 9
309
- #
310
- def should_ensure_length_is(attribute, length, opts = {})
311
- message = get_options!([opts], :message)
312
- message ||= default_error_message(:wrong_length, :count => length)
313
-
314
- klass = model_class
315
-
316
- should "not allow #{attribute} to be less than #{length} chars long" do
317
- min_value = "x" * (length - 1)
318
- assert_bad_value(klass, attribute, min_value, message)
319
- end
320
-
321
- should "not allow #{attribute} to be greater than #{length} chars long" do
322
- max_value = "x" * (length + 1)
323
- assert_bad_value(klass, attribute, max_value, message)
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
+ klass = model_class
192
+ good_values.each do |value|
193
+ matcher = allow_value(value).for(attribute)
194
+ should matcher.description do
195
+ assert_accepts matcher, get_instance_of(klass)
324
196
  end
197
+ end
198
+ end
325
199
 
326
- should "allow #{attribute} to be #{length} chars long" do
327
- valid_value = "x" * (length)
328
- assert_good_value(klass, attribute, valid_value, message)
329
- end
200
+ # Ensures that the length of the attribute is in the given range
201
+ #
202
+ # If an instance variable has been created in the setup named after the
203
+ # model being tested, then this method will use that. Otherwise, it will
204
+ # create a new instance to test against.
205
+ #
206
+ # Options:
207
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
208
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
209
+ # * <tt>:long_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.too_long') % range.last</tt>
211
+ #
212
+ # Example:
213
+ # should_ensure_length_in_range :password, (6..20)
214
+ #
215
+ def should_ensure_length_in_range(attribute, range, opts = {})
216
+ short_message, long_message = get_options!([opts],
217
+ :short_message,
218
+ :long_message)
219
+ klass = model_class
220
+
221
+ matcher = ensure_length_of(attribute).
222
+ is_at_least(range.first).
223
+ with_short_message(short_message).
224
+ is_at_most(range.last).
225
+ with_long_message(long_message)
226
+
227
+ should matcher.description do
228
+ assert_accepts matcher, get_instance_of(klass)
330
229
  end
230
+ end
331
231
 
332
- # Ensure that the attribute is in the range specified
333
- #
334
- # If an instance variable has been created in the setup named after the
335
- # model being tested, then this method will use that. Otherwise, it will
336
- # create a new instance to test against.
337
- #
338
- # Options:
339
- # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
340
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
341
- # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
342
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
343
- #
344
- # Example:
345
- # should_ensure_value_in_range :age, (0..100)
346
- #
347
- def should_ensure_value_in_range(attribute, range, opts = {})
348
- low_message, high_message = get_options!([opts], :low_message, :high_message)
349
- low_message ||= default_error_message(:inclusion)
350
- high_message ||= default_error_message(:inclusion)
351
-
352
- klass = model_class
353
- min = range.first
354
- max = range.last
355
-
356
- should "not allow #{attribute} to be less than #{min}" do
357
- v = min - 1
358
- assert_bad_value(klass, attribute, v, low_message)
359
- end
232
+ # Ensures that the length of the attribute is at least a certain length
233
+ #
234
+ # If an instance variable has been created in the setup named after the
235
+ # model being tested, then this method will use that. Otherwise, it will
236
+ # create a new instance to test against.
237
+ #
238
+ # Options:
239
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
240
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % min_length</tt>
241
+ #
242
+ # Example:
243
+ # should_ensure_length_at_least :name, 3
244
+ #
245
+ def should_ensure_length_at_least(attribute, min_length, opts = {})
246
+ short_message = get_options!([opts], :short_message)
247
+ klass = model_class
360
248
 
361
- should "allow #{attribute} to be #{min}" do
362
- v = min
363
- assert_good_value(klass, attribute, v, low_message)
364
- end
249
+ matcher = ensure_length_of(attribute).
250
+ is_at_least(min_length).
251
+ with_short_message(short_message)
365
252
 
366
- should "not allow #{attribute} to be more than #{max}" do
367
- v = max + 1
368
- assert_bad_value(klass, attribute, v, high_message)
369
- end
253
+ should matcher.description do
254
+ assert_accepts matcher, get_instance_of(klass)
255
+ end
256
+ end
370
257
 
371
- should "allow #{attribute} to be #{max}" do
372
- v = max
373
- assert_good_value(klass, attribute, v, high_message)
374
- end
258
+ # Ensures that the length of the attribute is exactly a certain length
259
+ #
260
+ # If an instance variable has been created in the setup named after the
261
+ # model being tested, then this method will use that. Otherwise, it will
262
+ # create a new instance to test against.
263
+ #
264
+ # Options:
265
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
266
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % length</tt>
267
+ #
268
+ # Example:
269
+ # should_ensure_length_is :ssn, 9
270
+ #
271
+ def should_ensure_length_is(attribute, length, opts = {})
272
+ message = get_options!([opts], :message)
273
+ klass = model_class
274
+ matcher = ensure_length_of(attribute).
275
+ is_equal_to(length).
276
+ with_message(message)
277
+
278
+ should matcher.description do
279
+ assert_accepts matcher, get_instance_of(klass)
375
280
  end
281
+ end
376
282
 
377
- # Ensure that the attribute is numeric
378
- #
379
- # If an instance variable has been created in the setup named after the
380
- # model being tested, then this method will use that. Otherwise, it will
381
- # create a new instance to test against.
382
- #
383
- # Options:
384
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
385
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
386
- #
387
- # Example:
388
- # should_only_allow_numeric_values_for :age
389
- #
390
- def should_only_allow_numeric_values_for(*attributes)
391
- message = get_options!(attributes, :message)
392
- message ||= default_error_message(:not_a_number)
393
- klass = model_class
394
- attributes.each do |attribute|
395
- attribute = attribute.to_sym
396
- should "only allow numeric values for #{attribute}" do
397
- assert_bad_value(klass, attribute, "abcd", message)
398
- end
399
- end
283
+ # Ensure that the attribute is in the range specified
284
+ #
285
+ # If an instance variable has been created in the setup named after the
286
+ # model being tested, then this method will use that. Otherwise, it will
287
+ # create a new instance to test against.
288
+ #
289
+ # Options:
290
+ # * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
291
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
292
+ # * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
293
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
294
+ #
295
+ # Example:
296
+ # should_ensure_value_in_range :age, (0..100)
297
+ #
298
+ def should_ensure_value_in_range(attribute, range, opts = {})
299
+ message, low_message, high_message = get_options!([opts],
300
+ :message,
301
+ :low_message,
302
+ :high_message)
303
+ klass = model_class
304
+ matcher = ensure_inclusion_of(attribute).
305
+ in_range(range).
306
+ with_message(message).
307
+ with_low_message(low_message).
308
+ with_high_message(high_message)
309
+ should matcher.description do
310
+ assert_accepts matcher, get_instance_of(klass)
400
311
  end
312
+ end
401
313
 
402
- # Ensures that the has_many relationship exists. Will also test that the
403
- # associated table has the required columns. Works with polymorphic
404
- # associations.
405
- #
406
- # Options:
407
- # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
408
- # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
409
- #
410
- # Example:
411
- # should_have_many :friends
412
- # should_have_many :enemies, :through => :friends
413
- # should_have_many :enemies, :dependent => :destroy
414
- #
415
- def should_have_many(*associations)
416
- through, dependent = get_options!(associations, :through, :dependent)
417
- klass = model_class
418
- associations.each do |association|
419
- name = "have many #{association}"
420
- name += " through #{through}" if through
421
- name += " dependent => #{dependent}" if dependent
422
- should name do
423
- reflection = klass.reflect_on_association(association)
424
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
425
- assert_equal :has_many, reflection.macro
426
-
427
- if through
428
- through_reflection = klass.reflect_on_association(through)
429
- assert through_reflection, "#{klass.name} does not have any relationship to #{through}"
430
- assert_equal(through, reflection.options[:through])
431
- end
432
-
433
- if dependent
434
- assert_equal dependent.to_s,
435
- reflection.options[:dependent].to_s,
436
- "#{association} should have #{dependent} dependency"
437
- end
438
-
439
- # Check for the existence of the foreign key on the other table
440
- unless reflection.options[:through]
441
- if reflection.options[:foreign_key]
442
- fk = reflection.options[:foreign_key]
443
- elsif reflection.options[:as]
444
- fk = reflection.options[:as].to_s.foreign_key
445
- else
446
- fk = reflection.primary_key_name
447
- end
448
-
449
- associated_klass_name = (reflection.options[:class_name] || association.to_s.classify)
450
- associated_klass = associated_klass_name.constantize
451
-
452
- assert associated_klass.column_names.include?(fk.to_s),
453
- "#{associated_klass.name} does not have a #{fk} foreign key."
454
- end
455
- end
314
+ # Ensure that the attribute is numeric
315
+ #
316
+ # If an instance variable has been created in the setup named after the
317
+ # model being tested, then this method will use that. Otherwise, it will
318
+ # create a new instance to test against.
319
+ #
320
+ # Options:
321
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
322
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.not_a_number')</tt>
323
+ #
324
+ # Example:
325
+ # should_validate_numericality_of :age
326
+ #
327
+ def should_validate_numericality_of(*attributes)
328
+ message = get_options!(attributes, :message)
329
+ klass = model_class
330
+ attributes.each do |attribute|
331
+ matcher = validate_numericality_of(attribute).
332
+ with_message(message)
333
+ should matcher.description do
334
+ assert_accepts matcher, get_instance_of(klass)
456
335
  end
457
336
  end
337
+ end
338
+
339
+ # Deprecated. See should_validate_numericality_of
340
+ def should_only_allow_numeric_values_for(*attributes)
341
+ warn "[DEPRECATION] should_only_allow_numeric_values_for is " <<
342
+ "deprecated. Use should_validate_numericality_of instead."
343
+ should_validate_numericality_of(*attributes)
344
+ end
458
345
 
459
- # Ensure that the has_one relationship exists. Will also test that the
460
- # associated table has the required columns. Works with polymorphic
461
- # associations.
462
- #
463
- # Options:
464
- # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
465
- #
466
- # Example:
467
- # should_have_one :god # unless hindu
468
- #
469
- def should_have_one(*associations)
470
- dependent = get_options!(associations, :dependent)
471
- klass = model_class
472
- associations.each do |association|
473
- name = "have one #{association}"
474
- name += " dependent => #{dependent}" if dependent
475
- should name do
476
- reflection = klass.reflect_on_association(association)
477
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
478
- assert_equal :has_one, reflection.macro
479
-
480
- associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
481
-
482
- if reflection.options[:foreign_key]
483
- fk = reflection.options[:foreign_key]
484
- elsif reflection.options[:as]
485
- fk = reflection.options[:as].to_s.foreign_key
486
- fk_type = fk.gsub(/_id$/, '_type')
487
- assert associated_klass.column_names.include?(fk_type),
488
- "#{associated_klass.name} does not have a #{fk_type} column."
489
- else
490
- fk = klass.name.foreign_key
491
- end
492
- assert associated_klass.column_names.include?(fk.to_s),
493
- "#{associated_klass.name} does not have a #{fk} foreign key."
494
-
495
- if dependent
496
- assert_equal dependent.to_s,
497
- reflection.options[:dependent].to_s,
498
- "#{association} should have #{dependent} dependency"
499
- end
500
- end
346
+ # Ensures that the has_many relationship exists. Will also test that the
347
+ # associated table has the required columns. Works with polymorphic
348
+ # associations.
349
+ #
350
+ # Options:
351
+ # * <tt>:through</tt> - association name for <tt>has_many :through</tt>
352
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
353
+ #
354
+ # Example:
355
+ # should_have_many :friends
356
+ # should_have_many :enemies, :through => :friends
357
+ # should_have_many :enemies, :dependent => :destroy
358
+ #
359
+ def should_have_many(*associations)
360
+ through, dependent = get_options!(associations, :through, :dependent)
361
+ klass = model_class
362
+ associations.each do |association|
363
+ matcher = have_many(association).through(through).dependent(dependent)
364
+ should matcher.description do
365
+ assert_accepts(matcher, klass.new)
501
366
  end
502
367
  end
368
+ end
503
369
 
504
- # Ensures that the has_and_belongs_to_many relationship exists, and that the join
505
- # table is in place.
506
- #
507
- # should_have_and_belong_to_many :posts, :cars
508
- #
509
- def should_have_and_belong_to_many(*associations)
510
- get_options!(associations)
511
- klass = model_class
512
-
513
- associations.each do |association|
514
- should "should have and belong to many #{association}" do
515
- reflection = klass.reflect_on_association(association)
516
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
517
- assert_equal :has_and_belongs_to_many, reflection.macro
518
- table = reflection.options[:join_table]
519
- assert ::ActiveRecord::Base.connection.tables.include?(table.to_s), "table #{table} doesn't exist"
520
- end
370
+ # Ensure that the has_one relationship exists. Will also test that the
371
+ # associated table has the required columns. Works with polymorphic
372
+ # associations.
373
+ #
374
+ # Options:
375
+ # * <tt>:dependent</tt> - tests that the association makes use of the dependent option.
376
+ #
377
+ # Example:
378
+ # should_have_one :god # unless hindu
379
+ #
380
+ def should_have_one(*associations)
381
+ dependent = get_options!(associations, :dependent)
382
+ klass = model_class
383
+ associations.each do |association|
384
+ matcher = have_one(association).dependent(dependent)
385
+ should matcher.description do
386
+ assert_accepts(matcher, klass.new)
521
387
  end
522
388
  end
389
+ end
523
390
 
524
- # Ensure that the belongs_to relationship exists.
525
- #
526
- # should_belong_to :parent
527
- #
528
- def should_belong_to(*associations)
529
- get_options!(associations)
530
- klass = model_class
531
- associations.each do |association|
532
- should "belong_to #{association}" do
533
- reflection = klass.reflect_on_association(association)
534
- assert reflection, "#{klass.name} does not have any relationship to #{association}"
535
- assert_equal :belongs_to, reflection.macro
536
-
537
- unless reflection.options[:polymorphic]
538
- associated_klass = (reflection.options[:class_name] || association.to_s.camelize).constantize
539
- fk = reflection.options[:foreign_key] || reflection.primary_key_name
540
- assert klass.column_names.include?(fk.to_s), "#{klass.name} does not have a #{fk} foreign key."
541
- end
542
- end
391
+ # Ensures that the has_and_belongs_to_many relationship exists, and that the join
392
+ # table is in place.
393
+ #
394
+ # should_have_and_belong_to_many :posts, :cars
395
+ #
396
+ def should_have_and_belong_to_many(*associations)
397
+ get_options!(associations)
398
+ klass = model_class
399
+
400
+ associations.each do |association|
401
+ matcher = have_and_belong_to_many(association)
402
+ should matcher.description do
403
+ assert_accepts(matcher, klass.new)
543
404
  end
544
405
  end
406
+ end
545
407
 
546
- # Ensure that the given class methods are defined on the model.
547
- #
548
- # should_have_class_methods :find, :destroy
549
- #
550
- def should_have_class_methods(*methods)
551
- get_options!(methods)
552
- klass = model_class
553
- methods.each do |method|
554
- should "respond to class method ##{method}" do
555
- assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
556
- end
408
+ # Ensure that the belongs_to relationship exists.
409
+ #
410
+ # should_belong_to :parent
411
+ #
412
+ def should_belong_to(*associations)
413
+ dependent = get_options!(associations, :dependent)
414
+ klass = model_class
415
+ associations.each do |association|
416
+ matcher = belong_to(association).dependent(dependent)
417
+ should matcher.description do
418
+ assert_accepts(matcher, klass.new)
557
419
  end
558
420
  end
421
+ end
559
422
 
560
- # Ensure that the given instance methods are defined on the model.
561
- #
562
- # should_have_instance_methods :email, :name, :name=
563
- #
564
- def should_have_instance_methods(*methods)
565
- get_options!(methods)
566
- klass = model_class
567
- methods.each do |method|
568
- should "respond to instance method ##{method}" do
569
- assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
570
- end
423
+ # Ensure that the given class methods are defined on the model.
424
+ #
425
+ # should_have_class_methods :find, :destroy
426
+ #
427
+ def should_have_class_methods(*methods)
428
+ get_options!(methods)
429
+ klass = model_class
430
+ methods.each do |method|
431
+ should "respond to class method ##{method}" do
432
+ assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
571
433
  end
572
434
  end
435
+ end
573
436
 
574
- # Ensure that the given columns are defined on the models backing SQL table.
575
- #
576
- # should_have_db_columns :id, :email, :name, :created_at
577
- #
578
- def should_have_db_columns(*columns)
579
- column_type = get_options!(columns, :type)
580
- klass = model_class
581
- columns.each do |name|
582
- test_name = "have column #{name}"
583
- test_name += " of type #{column_type}" if column_type
584
- should test_name do
585
- column = klass.columns.detect {|c| c.name == name.to_s }
586
- assert column, "#{klass.name} does not have column #{name}"
587
- end
437
+ # Ensure that the given instance methods are defined on the model.
438
+ #
439
+ # should_have_instance_methods :email, :name, :name=
440
+ #
441
+ def should_have_instance_methods(*methods)
442
+ get_options!(methods)
443
+ klass = model_class
444
+ methods.each do |method|
445
+ should "respond to instance method ##{method}" do
446
+ assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
588
447
  end
589
448
  end
449
+ end
590
450
 
591
- # Ensure that the given column is defined on the models backing SQL table. The options are the same as
592
- # the instance variables defined on the column definition: :precision, :limit, :default, :null,
593
- # :primary, :type, :scale, and :sql_type.
594
- #
595
- # should_have_db_column :email, :type => "string", :default => nil, :precision => nil, :limit => 255,
596
- # :null => true, :primary => false, :scale => nil, :sql_type => 'varchar(255)'
597
- #
598
- def should_have_db_column(name, opts = {})
599
- klass = model_class
600
- test_name = "have column named :#{name}"
601
- test_name += " with options " + opts.inspect unless opts.empty?
602
- should test_name do
603
- column = klass.columns.detect {|c| c.name == name.to_s }
604
- assert column, "#{klass.name} does not have column #{name}"
605
- opts.each do |k, v|
606
- assert_equal column.instance_variable_get("@#{k}").to_s, v.to_s, ":#{name} column on table for #{klass} does not match option :#{k}"
607
- end
451
+ # Ensure that the given columns are defined on the models backing SQL table.
452
+ # Also aliased to should_have_index for readability.
453
+ # Takes the same options available in migrations:
454
+ # :type, :precision, :limit, :default, :null, and :scale
455
+ #
456
+ # Examples:
457
+ #
458
+ # should_have_db_columns :id, :email, :name, :created_at
459
+ #
460
+ # should_have_db_column :email, :type => "string", :limit => 255
461
+ # should_have_db_column :salary, :decimal, :precision => 15, :scale => 2
462
+ # should_have_db_column :admin, :default => false, :null => false
463
+ #
464
+ def should_have_db_columns(*columns)
465
+ column_type, precision, limit, default, null, scale, sql_type =
466
+ get_options!(columns, :type, :precision, :limit,
467
+ :default, :null, :scale, :sql_type)
468
+ klass = model_class
469
+ columns.each do |name|
470
+ matcher = have_db_column(name).
471
+ of_type(column_type).
472
+ with_options(:precision => precision, :limit => limit,
473
+ :default => default, :null => null,
474
+ :scale => scale, :sql_type => sql_type)
475
+ should matcher.description do
476
+ assert_accepts(matcher, klass.new)
608
477
  end
609
478
  end
479
+ end
480
+
481
+ alias_method :should_have_db_column, :should_have_db_columns
610
482
 
611
- # Ensures that there are DB indices on the given columns or tuples of columns.
612
- # Also aliased to should_have_index for readability
613
- #
614
- # should_have_indices :email, :name, [:commentable_type, :commentable_id]
615
- # should_have_index :age
616
- #
617
- def should_have_indices(*columns)
618
- table = model_class.table_name
619
- indices = ::ActiveRecord::Base.connection.indexes(table).map(&:columns)
620
-
621
- columns.each do |column|
622
- should "have index on #{table} for #{column.inspect}" do
623
- columns = [column].flatten.map(&:to_s)
624
- assert_contains(indices, columns)
625
- end
483
+ # Ensures that there are DB indices on the given columns or tuples of columns.
484
+ # Also aliased to should_have_index for readability
485
+ #
486
+ # Options:
487
+ # * <tt>:unique</tt> - whether or not the index has a unique
488
+ # constraint. Use <tt>true</tt> to explicitly test for a unique
489
+ # constraint. Use <tt>false</tt> to explicitly test for a non-unique
490
+ # constraint. Use <tt>nil</tt> if you don't care whether the index is
491
+ # unique or not. Default = <tt>nil</tt>
492
+ #
493
+ # Examples:
494
+ #
495
+ # should_have_indices :email, :name, [:commentable_type, :commentable_id]
496
+ # should_have_index :age
497
+ # should_have_index :ssn, :unique => true
498
+ #
499
+ def should_have_indices(*columns)
500
+ unique = get_options!(columns, :unique)
501
+ klass = model_class
502
+
503
+ columns.each do |column|
504
+ matcher = have_index(column).unique(unique)
505
+ should matcher.description do
506
+ assert_accepts(matcher, klass.new)
626
507
  end
627
508
  end
509
+ end
628
510
 
629
- alias_method :should_have_index, :should_have_indices
630
-
631
- # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
632
- #
633
- # If an instance variable has been created in the setup named after the
634
- # model being tested, then this method will use that. Otherwise, it will
635
- # create a new instance to test against.
636
- #
637
- # Options:
638
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
639
- # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
640
- #
641
- # Example:
642
- # should_require_acceptance_of :eula
643
- #
644
- def should_require_acceptance_of(*attributes)
645
- message = get_options!(attributes, :message)
646
- message ||= default_error_message(:accepted)
647
- klass = model_class
648
-
649
- attributes.each do |attribute|
650
- should "require #{attribute} to be accepted" do
651
- assert_bad_value(klass, attribute, false, message)
652
- end
511
+ alias_method :should_have_index, :should_have_indices
512
+
513
+ # Ensures that the model cannot be saved if one of the attributes listed is not accepted.
514
+ #
515
+ # If an instance variable has been created in the setup named after the
516
+ # model being tested, then this method will use that. Otherwise, it will
517
+ # create a new instance to test against.
518
+ #
519
+ # Options:
520
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
521
+ # Regexp or string. Default = <tt>I18n.translate('activerecord.errors.messages.accepted')</tt>
522
+ #
523
+ # Example:
524
+ # should_validate_acceptance_of :eula
525
+ #
526
+ def should_validate_acceptance_of(*attributes)
527
+ message = get_options!(attributes, :message)
528
+ klass = model_class
529
+
530
+ attributes.each do |attribute|
531
+ matcher = validate_acceptance_of(attribute).with_message(message)
532
+ should matcher.description do
533
+ assert_accepts matcher, get_instance_of(klass)
653
534
  end
654
535
  end
536
+ end
655
537
 
656
- # Ensures that the model has a method named scope_name that returns a NamedScope object with the
657
- # proxy options set to the options you supply. scope_name can be either a symbol, or a method
658
- # call which will be evaled against the model. The eval'd method call has access to all the same
659
- # instance variables that a should statement would.
660
- #
661
- # Options: Any of the options that the named scope would pass on to find.
662
- #
663
- # Example:
664
- #
665
- # should_have_named_scope :visible, :conditions => {:visible => true}
666
- #
667
- # Passes for
668
- #
669
- # named_scope :visible, :conditions => {:visible => true}
670
- #
671
- # Or for
672
- #
673
- # def self.visible
674
- # scoped(:conditions => {:visible => true})
675
- # end
676
- #
677
- # You can test lambdas or methods that return ActiveRecord#scoped calls:
678
- #
679
- # should_have_named_scope 'recent(5)', :limit => 5
680
- # should_have_named_scope 'recent(1)', :limit => 1
681
- #
682
- # Passes for
683
- # named_scope :recent, lambda {|c| {:limit => c}}
684
- #
685
- # Or for
686
- #
687
- # def self.recent(c)
688
- # scoped(:limit => c)
689
- # end
690
- #
691
- def should_have_named_scope(scope_call, *args)
692
- klass = model_class
693
- scope_opts = args.extract_options!
694
- scope_call = scope_call.to_s
695
-
696
- context scope_call do
697
- setup do
698
- @scope = eval("#{klass}.#{scope_call}")
699
- end
700
-
701
- should "return a scope object" do
702
- assert_equal ::ActiveRecord::NamedScope::Scope, @scope.class
703
- end
704
-
705
- unless scope_opts.empty?
706
- should "scope itself to #{scope_opts.inspect}" do
707
- assert_equal scope_opts, @scope.proxy_options
708
- end
709
- end
710
- end
538
+ # Deprecated. See should_validate_uniqueness_of
539
+ def should_require_acceptance_of(*attributes)
540
+ warn "[DEPRECATION] should_require_acceptance_of is deprecated. " <<
541
+ "Use should_validate_acceptance_of instead."
542
+ should_validate_acceptance_of(*attributes)
543
+ end
544
+
545
+ # Ensures that the model has a method named scope_name that returns a NamedScope object with the
546
+ # proxy options set to the options you supply. scope_name can be either a symbol, or a method
547
+ # call which will be evaled against the model. The eval'd method call has access to all the same
548
+ # instance variables that a should statement would.
549
+ #
550
+ # Options: Any of the options that the named scope would pass on to find.
551
+ #
552
+ # Example:
553
+ #
554
+ # should_have_named_scope :visible, :conditions => {:visible => true}
555
+ #
556
+ # Passes for
557
+ #
558
+ # named_scope :visible, :conditions => {:visible => true}
559
+ #
560
+ # Or for
561
+ #
562
+ # def self.visible
563
+ # scoped(:conditions => {:visible => true})
564
+ # end
565
+ #
566
+ # You can test lambdas or methods that return ActiveRecord#scoped calls:
567
+ #
568
+ # should_have_named_scope 'recent(5)', :limit => 5
569
+ # should_have_named_scope 'recent(1)', :limit => 1
570
+ #
571
+ # Passes for
572
+ # named_scope :recent, lambda {|c| {:limit => c}}
573
+ #
574
+ # Or for
575
+ #
576
+ # def self.recent(c)
577
+ # scoped(:limit => c)
578
+ # end
579
+ #
580
+ def should_have_named_scope(scope_call, find_options = nil)
581
+ klass = model_class
582
+ matcher = have_named_scope(scope_call).finding(find_options)
583
+ should matcher.description do
584
+ assert_accepts matcher.in_context(self), klass.new
711
585
  end
712
586
  end
713
587
  end