remarkable_activerecord 3.1.8 → 3.1.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CHANGELOG +140 -138
  2. data/LICENSE +20 -20
  3. data/README +80 -80
  4. data/lib/remarkable_activerecord.rb +29 -29
  5. data/lib/remarkable_activerecord/base.rb +248 -237
  6. data/lib/remarkable_activerecord/describe.rb +27 -27
  7. data/lib/remarkable_activerecord/human_names.rb +36 -36
  8. data/lib/remarkable_activerecord/matchers/accept_nested_attributes_for_matcher.rb +30 -30
  9. data/lib/remarkable_activerecord/matchers/allow_mass_assignment_of_matcher.rb +59 -59
  10. data/lib/remarkable_activerecord/matchers/allow_values_for_matcher.rb +85 -94
  11. data/lib/remarkable_activerecord/matchers/association_matcher.rb +283 -283
  12. data/lib/remarkable_activerecord/matchers/have_column_matcher.rb +68 -68
  13. data/lib/remarkable_activerecord/matchers/have_default_scope_matcher.rb +38 -38
  14. data/lib/remarkable_activerecord/matchers/have_index_matcher.rb +73 -73
  15. data/lib/remarkable_activerecord/matchers/have_readonly_attributes_matcher.rb +30 -30
  16. data/lib/remarkable_activerecord/matchers/have_scope_matcher.rb +85 -85
  17. data/lib/remarkable_activerecord/matchers/validate_acceptance_of_matcher.rb +50 -50
  18. data/lib/remarkable_activerecord/matchers/validate_associated_matcher.rb +97 -97
  19. data/lib/remarkable_activerecord/matchers/validate_confirmation_of_matcher.rb +44 -44
  20. data/lib/remarkable_activerecord/matchers/validate_exclusion_of_matcher.rb +53 -53
  21. data/lib/remarkable_activerecord/matchers/validate_inclusion_of_matcher.rb +52 -52
  22. data/lib/remarkable_activerecord/matchers/validate_length_of_matcher.rb +150 -150
  23. data/lib/remarkable_activerecord/matchers/validate_numericality_of_matcher.rb +181 -181
  24. data/lib/remarkable_activerecord/matchers/validate_presence_of_matcher.rb +29 -29
  25. data/lib/remarkable_activerecord/matchers/validate_uniqueness_of_matcher.rb +233 -233
  26. data/locale/en.yml +261 -261
  27. data/spec/accept_nested_attributes_for_matcher_spec.rb +1 -1
  28. data/spec/allow_mass_assignment_of_matcher_spec.rb +90 -82
  29. data/spec/allow_values_for_matcher_spec.rb +72 -63
  30. data/spec/association_matcher_spec.rb +612 -612
  31. data/spec/describe_spec.rb +3 -3
  32. data/spec/have_column_matcher_spec.rb +73 -73
  33. data/spec/have_default_scope_matcher_spec.rb +1 -1
  34. data/spec/have_index_matcher_spec.rb +87 -87
  35. data/spec/have_readonly_attributes_matcher_spec.rb +47 -47
  36. data/spec/have_scope_matcher_spec.rb +77 -77
  37. data/spec/model_builder.rb +101 -101
  38. data/spec/rcov.opts +1 -1
  39. data/spec/spec.opts +4 -4
  40. data/spec/spec_helper.rb +27 -27
  41. data/spec/validate_acceptance_of_matcher_spec.rb +68 -68
  42. data/spec/validate_associated_matcher_spec.rb +121 -121
  43. data/spec/validate_confirmation_of_matcher_spec.rb +58 -58
  44. data/spec/validate_length_of_matcher_spec.rb +218 -218
  45. data/spec/validate_numericality_of_matcher_spec.rb +179 -179
  46. data/spec/validate_presence_of_matcher_spec.rb +56 -56
  47. data/spec/validate_uniqueness_of_matcher_spec.rb +164 -164
  48. metadata +5 -5
@@ -1,30 +1,30 @@
1
- # Load Remarkable
2
- unless Object.const_defined?('Remarkable')
3
- begin
4
- require 'remarkable'
5
- rescue LoadError
6
- require 'rubygems'
7
- gem 'remarkable'
8
- require 'remarkable'
9
- end
10
- end
11
-
12
- # Load Remarkable ActiveRecord files
13
- dir = File.dirname(__FILE__)
1
+ # Load Remarkable
2
+ unless Object.const_defined?('Remarkable')
3
+ begin
4
+ require 'remarkable'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ gem 'remarkable'
8
+ require 'remarkable'
9
+ end
10
+ end
11
+
12
+ # Load Remarkable ActiveRecord files
13
+ dir = File.dirname(__FILE__)
14
14
  require File.join(dir, 'remarkable_activerecord', 'base')
15
- require File.join(dir, 'remarkable_activerecord', 'describe')
16
- require File.join(dir, 'remarkable_activerecord', 'human_names')
17
-
18
- # Add locale
19
- Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml')
20
-
21
- # Add matchers
22
- Dir[File.join(dir, 'remarkable_activerecord', 'matchers', '*.rb')].each do |file|
23
- require file
24
- end
25
-
26
- # By default, ActiveRecord matchers are not included in any example group.
27
- # The responsable for this is RemarkableRails. If you are using ActiveRecord
28
- # without Rails, put the line below in your spec_helper to include ActiveRecord
29
- # matchers into rspec globally.
30
- # Remarkable.include_matchers!(Remarkable::ActiveRecord, Spec::Example::ExampleGroup)
15
+ require File.join(dir, 'remarkable_activerecord', 'describe')
16
+ require File.join(dir, 'remarkable_activerecord', 'human_names')
17
+
18
+ # Add locale
19
+ Remarkable.add_locale File.join(dir, '..', 'locale', 'en.yml')
20
+
21
+ # Add matchers
22
+ Dir[File.join(dir, 'remarkable_activerecord', 'matchers', '*.rb')].each do |file|
23
+ require file
24
+ end
25
+
26
+ # By default, ActiveRecord matchers are not included in any example group.
27
+ # The responsable for this is RemarkableRails. If you are using ActiveRecord
28
+ # without Rails, put the line below in your spec_helper to include ActiveRecord
29
+ # matchers into rspec globally.
30
+ # Remarkable.include_matchers!(Remarkable::ActiveRecord, Spec::Example::ExampleGroup)
@@ -1,237 +1,248 @@
1
- module Remarkable
2
- module ActiveRecord
3
- class Base < Remarkable::Base
4
- I18N_COLLECTION = [ :attributes, :associations ]
5
-
6
- # Provides a way to send options to all ActiveRecord matchers.
7
- #
8
- # validates_presence_of(:name).with_options(:allow_nil => false)
9
- #
10
- # Is equivalent to:
11
- #
12
- # validates_presence_of(:name, :allow_nil => false)
13
- #
14
- def with_options(opts={})
15
- @options.merge!(opts)
16
- self
17
- end
18
-
19
- protected
20
-
21
- # Checks for the given key in @options, if it exists and it's true,
22
- # tests that the value is bad, otherwise tests that the value is good.
23
- #
24
- # It accepts the key to check for, the value that is used for testing
25
- # and an @options key where the message to search for is.
26
- #
27
- def assert_bad_or_good_if_key(key, value, message_key=:message) #:nodoc:
28
- return positive? unless @options.key?(key)
29
-
30
- if @options[key]
31
- return bad?(value, message_key), :not => not_word
32
- else
33
- return good?(value, message_key), :not => ''
34
- end
35
- end
36
-
37
- # Checks for the given key in @options, if it exists and it's true,
38
- # tests that the value is good, otherwise tests that the value is bad.
39
- #
40
- # It accepts the key to check for, the value that is used for testing
41
- # and an @options key where the message to search for is.
42
- #
43
- def assert_good_or_bad_if_key(key, value, message_key=:message) #:nodoc:
44
- return positive? unless @options.key?(key)
45
-
46
- if @options[key]
47
- return good?(value, message_key), :not => ''
48
- else
49
- return bad?(value, message_key), :not => not_word
50
- end
51
- end
52
-
53
- # Default allow_nil? validation. It accepts the message_key which is
54
- # the key which contain the message in @options.
55
- #
56
- # It also gets an allow_nil message on remarkable.active_record.allow_nil
57
- # to be used as default.
58
- #
59
- def allow_nil?(message_key=:message) #:nodoc:
60
- assert_good_or_bad_if_key(:allow_nil, nil, message_key)
61
- end
62
-
63
- # Default allow_blank? validation. It accepts the message_key which is
64
- # the key which contain the message in @options.
65
- #
66
- # It also gets an allow_blank message on remarkable.active_record.allow_blank
67
- # to be used as default.
68
- #
69
- def allow_blank?(message_key=:message) #:nodoc:
70
- assert_good_or_bad_if_key(:allow_blank, '', message_key)
71
- end
72
-
73
- # Shortcut for assert_good_value.
74
- #
75
- def good?(value, message_sym=:message) #:nodoc:
76
- assert_good_value(@subject, @attribute, value, @options[message_sym])
77
- end
78
-
79
- # Shortcut for assert_bad_value.
80
- #
81
- def bad?(value, message_sym=:message) #:nodoc:
82
- assert_bad_value(@subject, @attribute, value, @options[message_sym])
83
- end
84
-
85
- # Asserts that an Active Record model validates with the passed
86
- # <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
87
- # contained within the list of errors for that attribute.
88
- #
89
- # assert_good_value(User.new, :email, "user@example.com")
90
- # assert_good_value(User.new, :ssn, "123456789", /length/)
91
- #
92
- # If a class is passed as the first argument, a new object will be
93
- # instantiated before the assertion. If an instance variable exists with
94
- # the same name as the class (underscored), that object will be used
95
- # instead.
96
- #
97
- # assert_good_value(User, :email, "user@example.com")
98
- #
99
- # @product = Product.new(:tangible => false)
100
- # assert_good_value(Product, :price, "0")
101
- #
102
- def assert_good_value(model, attribute, value, error_message_to_avoid=//) # :nodoc:
103
- model.send("#{attribute}=", value)
104
-
105
- return true if model.valid?
106
-
107
- error_message_to_avoid = error_message_from_model(model, attribute, error_message_to_avoid)
108
- assert_does_not_contain(model.errors.on(attribute), error_message_to_avoid)
109
- end
110
-
111
- # Asserts that an Active Record model invalidates the passed
112
- # <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
113
- # contained within the list of errors for that attribute.
114
- #
115
- # assert_bad_value(User.new, :email, "invalid")
116
- # assert_bad_value(User.new, :ssn, "123", /length/)
117
- #
118
- # If a class is passed as the first argument, a new object will be
119
- # instantiated before the assertion. If an instance variable exists with
120
- # the same name as the class (underscored), that object will be used
121
- # instead.
122
- #
123
- # assert_bad_value(User, :email, "invalid")
124
- #
125
- # @product = Product.new(:tangible => true)
126
- # assert_bad_value(Product, :price, "0")
127
- #
128
- def assert_bad_value(model, attribute, value, error_message_to_expect=:invalid) #:nodoc:
129
- model.send("#{attribute}=", value)
130
-
131
- return false if model.valid? || model.errors.on(attribute).blank?
132
-
133
- error_message_to_expect = error_message_from_model(model, attribute, error_message_to_expect)
134
- assert_contains(model.errors.on(attribute), error_message_to_expect)
135
- end
136
-
137
- # Return the error message to be checked. If the message is not a Symbol
138
- # neither a Hash, it returns the own message.
139
- #
140
- # But the nice thing is that when the message is a Symbol we get the error
141
- # messsage from within the model, using already existent structure inside
142
- # ActiveRecord.
143
- #
144
- # This allows a couple things from the user side:
145
- #
146
- # 1. Specify symbols in their tests:
147
- #
148
- # should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => :inclusion)
149
- #
150
- # As we know, allow_values_for searches for a :invalid message. So if we
151
- # were testing a validates_inclusion_of with allow_values_for, previously
152
- # we had to do something like this:
153
- #
154
- # should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => 'not included in list')
155
- #
156
- # Now everything gets resumed to a Symbol.
157
- #
158
- # 2. Do not worry with specs if their are using I18n API properly.
159
- #
160
- # As we know, I18n API provides several interpolation options besides
161
- # fallback when creating error messages. If the user changed the message,
162
- # macros would start to pass when they shouldn't.
163
- #
164
- # Using the underlying mechanism inside ActiveRecord makes us free from
165
- # all thos errors.
166
- #
167
- # We replace {{count}} interpolation for 12345 which later is replaced
168
- # by a regexp which contains \d+.
169
- #
170
- def error_message_from_model(model, attribute, message) #:nodoc:
171
- if message.is_a? Symbol
172
- message = if RAILS_I18N # Rails >= 2.2
173
- model.errors.generate_message(attribute, message, :count => '12345')
174
- else # Rails <= 2.1
175
- ::ActiveRecord::Errors.default_error_messages[message] % '12345'
176
- end
177
-
178
- if message =~ /12345/
179
- message = Regexp.escape(message)
180
- message.gsub!('12345', '\d+')
181
- message = /#{message}/
182
- end
183
- end
184
-
185
- message
186
- end
187
-
188
- # Asserts that the given collection does not contain item x. If x is a
189
- # regular expression, ensure that none of the elements from the collection
190
- # match x.
191
- #
192
- def assert_does_not_contain(collection, x) #:nodoc:
193
- !assert_contains(collection, x)
194
- end
195
-
196
- # Changes how collection are interpolated to provide localized names
197
- # whenever is possible.
198
- #
199
- def collection_interpolation #:nodoc:
200
- described_class = if @subject
201
- subject_class
202
- elsif @spec
203
- @spec.send(:described_class)
204
- end
205
-
206
- if i18n_collection? && described_class.respond_to?(:human_attribute_name)
207
- options = {}
208
-
209
- collection_name = self.class.matcher_arguments[:collection].to_sym
210
- if collection = instance_variable_get("@#{collection_name}")
211
- collection = collection.map do |attr|
212
- described_class.human_attribute_name(attr.to_s, :locale => Remarkable.locale).downcase
213
- end
214
- options[collection_name] = array_to_sentence(collection)
215
- end
216
-
217
- object_name = self.class.matcher_arguments[:as]
218
- if object = instance_variable_get("@#{object_name}")
219
- object = described_class.human_attribute_name(object.to_s, :locale => Remarkable.locale).downcase
220
- options[object_name] = object
221
- end
222
-
223
- options
224
- else
225
- super
226
- end
227
- end
228
-
229
- # Returns true if the given collection should be translated.
230
- #
231
- def i18n_collection? #:nodoc:
232
- RAILS_I18N && I18N_COLLECTION.include?(self.class.matcher_arguments[:collection])
233
- end
234
-
235
- end
236
- end
237
- end
1
+ module Remarkable
2
+ module ActiveRecord
3
+ class Base < Remarkable::Base
4
+ I18N_COLLECTION = [ :attributes, :associations ]
5
+
6
+ # Provides a way to send options to all ActiveRecord matchers.
7
+ #
8
+ # validates_presence_of(:name).with_options(:allow_nil => false)
9
+ #
10
+ # Is equivalent to:
11
+ #
12
+ # validates_presence_of(:name, :allow_nil => false)
13
+ #
14
+ def with_options(opts={})
15
+ @options.merge!(opts)
16
+ self
17
+ end
18
+
19
+ protected
20
+
21
+ # Overwrite subject_name to provide I18n.
22
+ #
23
+ def subject_name
24
+ nil unless @subject
25
+ if subject_class.respond_to?(:human_name)
26
+ subject_class.human_name(:locale => Remarkable.locale)
27
+ else
28
+ subject_class.name
29
+ end
30
+ end
31
+
32
+ # Checks for the given key in @options, if it exists and it's true,
33
+ # tests that the value is bad, otherwise tests that the value is good.
34
+ #
35
+ # It accepts the key to check for, the value that is used for testing
36
+ # and an @options key where the message to search for is.
37
+ #
38
+ def assert_bad_or_good_if_key(key, value, message_key=:message) #:nodoc:
39
+ return positive? unless @options.key?(key)
40
+
41
+ if @options[key]
42
+ return bad?(value, message_key), :not => not_word
43
+ else
44
+ return good?(value, message_key), :not => ''
45
+ end
46
+ end
47
+
48
+ # Checks for the given key in @options, if it exists and it's true,
49
+ # tests that the value is good, otherwise tests that the value is bad.
50
+ #
51
+ # It accepts the key to check for, the value that is used for testing
52
+ # and an @options key where the message to search for is.
53
+ #
54
+ def assert_good_or_bad_if_key(key, value, message_key=:message) #:nodoc:
55
+ return positive? unless @options.key?(key)
56
+
57
+ if @options[key]
58
+ return good?(value, message_key), :not => ''
59
+ else
60
+ return bad?(value, message_key), :not => not_word
61
+ end
62
+ end
63
+
64
+ # Default allow_nil? validation. It accepts the message_key which is
65
+ # the key which contain the message in @options.
66
+ #
67
+ # It also gets an allow_nil message on remarkable.active_record.allow_nil
68
+ # to be used as default.
69
+ #
70
+ def allow_nil?(message_key=:message) #:nodoc:
71
+ assert_good_or_bad_if_key(:allow_nil, nil, message_key)
72
+ end
73
+
74
+ # Default allow_blank? validation. It accepts the message_key which is
75
+ # the key which contain the message in @options.
76
+ #
77
+ # It also gets an allow_blank message on remarkable.active_record.allow_blank
78
+ # to be used as default.
79
+ #
80
+ def allow_blank?(message_key=:message) #:nodoc:
81
+ assert_good_or_bad_if_key(:allow_blank, '', message_key)
82
+ end
83
+
84
+ # Shortcut for assert_good_value.
85
+ #
86
+ def good?(value, message_sym=:message) #:nodoc:
87
+ assert_good_value(@subject, @attribute, value, @options[message_sym])
88
+ end
89
+
90
+ # Shortcut for assert_bad_value.
91
+ #
92
+ def bad?(value, message_sym=:message) #:nodoc:
93
+ assert_bad_value(@subject, @attribute, value, @options[message_sym])
94
+ end
95
+
96
+ # Asserts that an Active Record model validates with the passed
97
+ # <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
98
+ # contained within the list of errors for that attribute.
99
+ #
100
+ # assert_good_value(User.new, :email, "user@example.com")
101
+ # assert_good_value(User.new, :ssn, "123456789", /length/)
102
+ #
103
+ # If a class is passed as the first argument, a new object will be
104
+ # instantiated before the assertion. If an instance variable exists with
105
+ # the same name as the class (underscored), that object will be used
106
+ # instead.
107
+ #
108
+ # assert_good_value(User, :email, "user@example.com")
109
+ #
110
+ # @product = Product.new(:tangible => false)
111
+ # assert_good_value(Product, :price, "0")
112
+ #
113
+ def assert_good_value(model, attribute, value, error_message_to_avoid=//) # :nodoc:
114
+ model.send("#{attribute}=", value)
115
+
116
+ return true if model.valid?
117
+
118
+ error_message_to_avoid = error_message_from_model(model, attribute, error_message_to_avoid)
119
+ assert_does_not_contain(model.errors.on(attribute), error_message_to_avoid)
120
+ end
121
+
122
+ # Asserts that an Active Record model invalidates the passed
123
+ # <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
124
+ # contained within the list of errors for that attribute.
125
+ #
126
+ # assert_bad_value(User.new, :email, "invalid")
127
+ # assert_bad_value(User.new, :ssn, "123", /length/)
128
+ #
129
+ # If a class is passed as the first argument, a new object will be
130
+ # instantiated before the assertion. If an instance variable exists with
131
+ # the same name as the class (underscored), that object will be used
132
+ # instead.
133
+ #
134
+ # assert_bad_value(User, :email, "invalid")
135
+ #
136
+ # @product = Product.new(:tangible => true)
137
+ # assert_bad_value(Product, :price, "0")
138
+ #
139
+ def assert_bad_value(model, attribute, value, error_message_to_expect=:invalid) #:nodoc:
140
+ model.send("#{attribute}=", value)
141
+
142
+ return false if model.valid? || model.errors.on(attribute).blank?
143
+
144
+ error_message_to_expect = error_message_from_model(model, attribute, error_message_to_expect)
145
+ assert_contains(model.errors.on(attribute), error_message_to_expect)
146
+ end
147
+
148
+ # Return the error message to be checked. If the message is not a Symbol
149
+ # neither a Hash, it returns the own message.
150
+ #
151
+ # But the nice thing is that when the message is a Symbol we get the error
152
+ # messsage from within the model, using already existent structure inside
153
+ # ActiveRecord.
154
+ #
155
+ # This allows a couple things from the user side:
156
+ #
157
+ # 1. Specify symbols in their tests:
158
+ #
159
+ # should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => :inclusion)
160
+ #
161
+ # As we know, allow_values_for searches for a :invalid message. So if we
162
+ # were testing a validates_inclusion_of with allow_values_for, previously
163
+ # we had to do something like this:
164
+ #
165
+ # should_allow_values_for(:shirt_size, 'S', 'M', 'L', :message => 'not included in list')
166
+ #
167
+ # Now everything gets resumed to a Symbol.
168
+ #
169
+ # 2. Do not worry with specs if their are using I18n API properly.
170
+ #
171
+ # As we know, I18n API provides several interpolation options besides
172
+ # fallback when creating error messages. If the user changed the message,
173
+ # macros would start to pass when they shouldn't.
174
+ #
175
+ # Using the underlying mechanism inside ActiveRecord makes us free from
176
+ # all thos errors.
177
+ #
178
+ # We replace {{count}} interpolation for 12345 which later is replaced
179
+ # by a regexp which contains \d+.
180
+ #
181
+ def error_message_from_model(model, attribute, message) #:nodoc:
182
+ if message.is_a? Symbol
183
+ message = if RAILS_I18N # Rails >= 2.2
184
+ model.errors.generate_message(attribute, message, :count => '12345')
185
+ else # Rails <= 2.1
186
+ ::ActiveRecord::Errors.default_error_messages[message] % '12345'
187
+ end
188
+
189
+ if message =~ /12345/
190
+ message = Regexp.escape(message)
191
+ message.gsub!('12345', '\d+')
192
+ message = /#{message}/
193
+ end
194
+ end
195
+
196
+ message
197
+ end
198
+
199
+ # Asserts that the given collection does not contain item x. If x is a
200
+ # regular expression, ensure that none of the elements from the collection
201
+ # match x.
202
+ #
203
+ def assert_does_not_contain(collection, x) #:nodoc:
204
+ !assert_contains(collection, x)
205
+ end
206
+
207
+ # Changes how collection are interpolated to provide localized names
208
+ # whenever is possible.
209
+ #
210
+ def collection_interpolation #:nodoc:
211
+ described_class = if @subject
212
+ subject_class
213
+ elsif @spec
214
+ @spec.send(:described_class)
215
+ end
216
+
217
+ if i18n_collection? && described_class.respond_to?(:human_attribute_name)
218
+ options = {}
219
+
220
+ collection_name = self.class.matcher_arguments[:collection].to_sym
221
+ if collection = instance_variable_get("@#{collection_name}")
222
+ collection = collection.map do |attr|
223
+ described_class.human_attribute_name(attr.to_s, :locale => Remarkable.locale).downcase
224
+ end
225
+ options[collection_name] = array_to_sentence(collection)
226
+ end
227
+
228
+ object_name = self.class.matcher_arguments[:as]
229
+ if object = instance_variable_get("@#{object_name}")
230
+ object = described_class.human_attribute_name(object.to_s, :locale => Remarkable.locale).downcase
231
+ options[object_name] = object
232
+ end
233
+
234
+ options
235
+ else
236
+ super
237
+ end
238
+ end
239
+
240
+ # Returns true if the given collection should be translated.
241
+ #
242
+ def i18n_collection? #:nodoc:
243
+ RAILS_I18N && I18N_COLLECTION.include?(self.class.matcher_arguments[:collection])
244
+ end
245
+
246
+ end
247
+ end
248
+ end