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