remarkable_activerecord 3.1.8 → 3.1.9

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 (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,4 +1,4 @@
1
- module Remarkable
1
+ module Remarkable
2
2
  module ActiveRecord
3
3
  # Holds ActiveRecord matchers.
4
4
  #
@@ -34,13 +34,13 @@ module Remarkable
34
34
  #
35
35
  # However, if you change the title using the I18n API, you don't need to
36
36
  # specify the message in your tests, because it's retrieved properly.
37
- #
38
- module Matchers
39
- class ValidatePresenceOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
40
- arguments :collection => :attributes, :as => :attribute
41
- optional :message
42
-
43
- collection_assertions :allow_nil?
37
+ #
38
+ module Matchers
39
+ class ValidatePresenceOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
40
+ arguments :collection => :attributes, :as => :attribute
41
+ optional :message
42
+
43
+ collection_assertions :allow_nil?
44
44
  default_options :message => :blank
45
45
 
46
46
  protected
@@ -60,24 +60,24 @@ module Remarkable
60
60
  false
61
61
  end
62
62
  end
63
-
64
- end
65
-
66
- # Ensures that the model cannot be saved if one of the attributes listed is not present.
67
- #
68
- # == Options
69
- #
70
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
71
- # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
72
- #
73
- # == Examples
74
- #
75
- # should_validate_presence_of :name, :phone_number
76
- # it { should validate_presence_of(:name, :phone_number) }
77
- #
78
- def validate_presence_of(*args, &block)
79
- ValidatePresenceOfMatcher.new(*args, &block).spec(self)
80
- end
81
- end
82
- end
83
- end
63
+
64
+ end
65
+
66
+ # Ensures that the model cannot be saved if one of the attributes listed is not present.
67
+ #
68
+ # == Options
69
+ #
70
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
71
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
72
+ #
73
+ # == Examples
74
+ #
75
+ # should_validate_presence_of :name, :phone_number
76
+ # it { should validate_presence_of(:name, :phone_number) }
77
+ #
78
+ def validate_presence_of(*args, &block)
79
+ ValidatePresenceOfMatcher.new(*args, &block).spec(self)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,233 +1,233 @@
1
- module Remarkable
2
- module ActiveRecord
3
- module Matchers
4
- class ValidateUniquenessOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
- arguments :collection => :attributes, :as => :attribute
6
-
7
- optional :message
8
- optional :scope, :splat => true
9
- optional :case_sensitive, :allow_nil, :allow_blank, :default => true
10
-
11
- collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?, :case_sensitive?,
12
- :valid_with_new_scope?, :allow_nil?, :allow_blank?
13
-
14
- default_options :message => :taken
15
-
16
- before_assert do
17
- @options[:scope] = [*@options[:scope]].compact if @options[:scope]
18
- end
19
-
20
- private
21
-
22
- # Tries to find an object in the database. If allow_nil and/or allow_blank
23
- # is given, we must find a record which is not nil or not blank.
24
- #
25
- # We should also ensure that the object retrieved from the database
26
- # is not the @subject.
27
- #
28
- # If any of these attempts fail, an error is raised.
29
- #
30
- def find_first_object?
31
- conditions, message = if @options[:allow_nil]
32
- [ ["#{@attribute} IS NOT NULL"], " with #{@attribute} not nil" ]
33
- elsif @options[:allow_blank]
34
- [ ["#{@attribute} != ''"], " with #{@attribute} not blank" ]
35
- else
36
- [ [], "" ]
37
- end
38
-
39
- unless @subject.new_record?
40
- primary_key = subject_class.primary_key
41
-
42
- message << " which is different from the subject record (the object being validated is the same as the one in the database)"
43
- conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
44
- end
45
-
46
- options = conditions.empty? ? {} : { :conditions => conditions.join(' AND ') }
47
-
48
- return true if @existing = subject_class.find(:first, options)
49
- raise ScriptError, "could not find a #{subject_class} record in the database" + message
50
- end
51
-
52
- # Set subject scope to be equal to the object found.
53
- #
54
- def responds_to_scope?
55
- (@options[:scope] || []).each do |scope|
56
- setter = :"#{scope}="
57
-
58
- return false, :method => setter unless @subject.respond_to?(setter)
59
- return false, :method => scope unless @existing.respond_to?(scope)
60
-
61
- @subject.send(setter, @existing.send(scope))
62
- end
63
- true
64
- end
65
-
66
- # Check if the attribute given is valid and if the validation fails for equal values.
67
- #
68
- def is_unique?
69
- @value = @existing.send(@attribute)
70
- return bad?(@value)
71
- end
72
-
73
- # If :case_sensitive is given and it's false, we swap the case of the
74
- # value used in :is_unique? and see if the test object remains valid.
75
- #
76
- # If :case_sensitive is given and it's true, we swap the case of the
77
- # value used in is_unique? and see if the test object is not valid.
78
- #
79
- # This validation will only occur if the test object is a String.
80
- #
81
- def case_sensitive?
82
- return true unless @value.is_a?(String)
83
- assert_good_or_bad_if_key(:case_sensitive, @value.swapcase)
84
- end
85
-
86
- # Now test that the object is valid when changing the scoped attribute.
87
- #
88
- def valid_with_new_scope?
89
- (@options[:scope] || []).each do |scope|
90
- setter = :"#{scope}="
91
-
92
- previous_scope_value = @subject.send(scope)
93
- @subject.send(setter, new_value_for_scope(scope))
94
- return false, :method => scope unless good?(@value)
95
-
96
- @subject.send(setter, previous_scope_value)
97
- end
98
- true
99
- end
100
-
101
- # Change the existing object attribute to nil to run allow nil
102
- # validations. If we find any problem while updating the @existing
103
- # record, it's because we can't save nil values in the database. So it
104
- # passes when :allow_nil is false, but should raise an error when
105
- # :allow_nil is true
106
- #
107
- def allow_nil?
108
- return true unless @options.key?(:allow_nil)
109
-
110
- begin
111
- @existing.update_attribute(@attribute, nil)
112
- rescue ::ActiveRecord::StatementInvalid => e
113
- raise ScriptError, "You declared that #{@attribute} accepts nil values in validate_uniqueness_of, " <<
114
- "but I cannot save nil values in the database, got: #{e.message}" if @options[:allow_nil]
115
- return true
116
- end
117
-
118
- super
119
- end
120
-
121
- # Change the existing object attribute to blank to run allow blank
122
- # validation. It uses the same logic as :allow_nil.
123
- #
124
- def allow_blank?
125
- return true unless @options.key?(:allow_blank)
126
-
127
- begin
128
- @existing.update_attribute(@attribute, '')
129
- rescue ::ActiveRecord::StatementInvalid => e
130
- raise ScriptError, "You declared that #{@attribute} accepts blank values in validate_uniqueness_of, " <<
131
- "but I cannot save blank values in the database, got: #{e.message}" if @options[:allow_blank]
132
- return true
133
- end
134
-
135
- super
136
- end
137
-
138
- # Returns a value to be used as new scope. It deals with four different
139
- # cases: date, time, boolean and stringfiable (everything that can be
140
- # converted to a string and the next value makes sense)
141
- #
142
- def new_value_for_scope(scope)
143
- column_type = if @existing.respond_to?(:column_for_attribute)
144
- @existing.column_for_attribute(scope)
145
- else
146
- nil
147
- end
148
-
149
- case column_type.type
150
- when :int, :integer, :float, :decimal
151
- new_value_for_stringfiable_scope(scope)
152
- when :datetime, :timestamp, :time
153
- Time.now + 10000
154
- when :date
155
- Date.today + 100
156
- when :boolean
157
- !@existing.send(scope)
158
- else
159
- new_value_for_stringfiable_scope(scope)
160
- end
161
- end
162
-
163
- # Returns a value to be used as scope by generating a range of values
164
- # and searching for them in the database.
165
- #
166
- def new_value_for_stringfiable_scope(scope)
167
- values = [(@existing.send(scope) || 999).next.to_s]
168
-
169
- # Generate a range of values to search in the database
170
- 100.times do
171
- values << values.last.next
172
- end
173
- conditions = { scope => values, @attribute => @value }
174
-
175
- # Get values from the database, get the scope attribute and map them to string.
176
- db_values = subject_class.find(:all, :conditions => conditions, :select => scope)
177
- db_values.map!{ |r| r.send(scope).to_s }
178
-
179
- if value_to_return = (values - db_values).first
180
- value_to_return
181
- else
182
- raise ScriptError, "Tried to find an unique scope value for #{scope} but I could not. " <<
183
- "The conditions hash was #{conditions.inspect} and it returned all records."
184
- end
185
- end
186
- end
187
-
188
- # Ensures that the model cannot be saved if one of the attributes listed
189
- # is not unique.
190
- #
191
- # Requires an existing record in the database. If you supply :allow_nil as
192
- # option, you need to have in the database a record which is not nil in the
193
- # given attributes. The same is required for allow_blank option.
194
- #
195
- # Notice that the record being validate should not be the same as in the
196
- # database. In other words, you can't do this:
197
- #
198
- # subject { Post.create!(@valid_attributes) }
199
- # should_validate_uniqueness_of :title
200
- #
201
- # But don't worry, if you eventually do that, a helpful error message
202
- # will be raised.
203
- #
204
- # == Options
205
- #
206
- # * <tt>:scope</tt> - field(s) to scope the uniqueness to.
207
- # * <tt>:case_sensitive</tt> - the matcher look for an exact match.
208
- # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
209
- # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
210
- # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
211
- # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
212
- #
213
- # == Examples
214
- #
215
- # it { should validate_uniqueness_of(:keyword, :username) }
216
- # it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
217
- # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
218
- #
219
- # should_validate_uniqueness_of :keyword, :username
220
- # should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
221
- # should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
222
- #
223
- # should_validate_uniqueness_of :email do |m|
224
- # m.scope = name
225
- # m.case_sensitive = false
226
- # end
227
- #
228
- def validate_uniqueness_of(*attributes, &block)
229
- ValidateUniquenessOfMatcher.new(*attributes, &block).spec(self)
230
- end
231
- end
232
- end
233
- end
1
+ module Remarkable
2
+ module ActiveRecord
3
+ module Matchers
4
+ class ValidateUniquenessOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
5
+ arguments :collection => :attributes, :as => :attribute
6
+
7
+ optional :message
8
+ optional :scope, :splat => true
9
+ optional :case_sensitive, :allow_nil, :allow_blank, :default => true
10
+
11
+ collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?, :case_sensitive?,
12
+ :valid_with_new_scope?, :allow_nil?, :allow_blank?
13
+
14
+ default_options :message => :taken
15
+
16
+ before_assert do
17
+ @options[:scope] = [*@options[:scope]].compact if @options[:scope]
18
+ end
19
+
20
+ private
21
+
22
+ # Tries to find an object in the database. If allow_nil and/or allow_blank
23
+ # is given, we must find a record which is not nil or not blank.
24
+ #
25
+ # We should also ensure that the object retrieved from the database
26
+ # is not the @subject.
27
+ #
28
+ # If any of these attempts fail, an error is raised.
29
+ #
30
+ def find_first_object?
31
+ conditions, message = if @options[:allow_nil]
32
+ [ ["#{@attribute} IS NOT NULL"], " with #{@attribute} not nil" ]
33
+ elsif @options[:allow_blank]
34
+ [ ["#{@attribute} != ''"], " with #{@attribute} not blank" ]
35
+ else
36
+ [ [], "" ]
37
+ end
38
+
39
+ unless @subject.new_record?
40
+ primary_key = subject_class.primary_key
41
+
42
+ message << " which is different from the subject record (the object being validated is the same as the one in the database)"
43
+ conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
44
+ end
45
+
46
+ options = conditions.empty? ? {} : { :conditions => conditions.join(' AND ') }
47
+
48
+ return true if @existing = subject_class.find(:first, options)
49
+ raise ScriptError, "could not find a #{subject_class} record in the database" + message
50
+ end
51
+
52
+ # Set subject scope to be equal to the object found.
53
+ #
54
+ def responds_to_scope?
55
+ (@options[:scope] || []).each do |scope|
56
+ setter = :"#{scope}="
57
+
58
+ return false, :method => setter unless @subject.respond_to?(setter)
59
+ return false, :method => scope unless @existing.respond_to?(scope)
60
+
61
+ @subject.send(setter, @existing.send(scope))
62
+ end
63
+ true
64
+ end
65
+
66
+ # Check if the attribute given is valid and if the validation fails for equal values.
67
+ #
68
+ def is_unique?
69
+ @value = @existing.send(@attribute)
70
+ return bad?(@value)
71
+ end
72
+
73
+ # If :case_sensitive is given and it's false, we swap the case of the
74
+ # value used in :is_unique? and see if the test object remains valid.
75
+ #
76
+ # If :case_sensitive is given and it's true, we swap the case of the
77
+ # value used in is_unique? and see if the test object is not valid.
78
+ #
79
+ # This validation will only occur if the test object is a String.
80
+ #
81
+ def case_sensitive?
82
+ return true unless @value.is_a?(String)
83
+ assert_good_or_bad_if_key(:case_sensitive, @value.swapcase)
84
+ end
85
+
86
+ # Now test that the object is valid when changing the scoped attribute.
87
+ #
88
+ def valid_with_new_scope?
89
+ (@options[:scope] || []).each do |scope|
90
+ setter = :"#{scope}="
91
+
92
+ previous_scope_value = @subject.send(scope)
93
+ @subject.send(setter, new_value_for_scope(scope))
94
+ return false, :method => scope unless good?(@value)
95
+
96
+ @subject.send(setter, previous_scope_value)
97
+ end
98
+ true
99
+ end
100
+
101
+ # Change the existing object attribute to nil to run allow nil
102
+ # validations. If we find any problem while updating the @existing
103
+ # record, it's because we can't save nil values in the database. So it
104
+ # passes when :allow_nil is false, but should raise an error when
105
+ # :allow_nil is true
106
+ #
107
+ def allow_nil?
108
+ return true unless @options.key?(:allow_nil)
109
+
110
+ begin
111
+ @existing.update_attribute(@attribute, nil)
112
+ rescue ::ActiveRecord::StatementInvalid => e
113
+ raise ScriptError, "You declared that #{@attribute} accepts nil values in validate_uniqueness_of, " <<
114
+ "but I cannot save nil values in the database, got: #{e.message}" if @options[:allow_nil]
115
+ return true
116
+ end
117
+
118
+ super
119
+ end
120
+
121
+ # Change the existing object attribute to blank to run allow blank
122
+ # validation. It uses the same logic as :allow_nil.
123
+ #
124
+ def allow_blank?
125
+ return true unless @options.key?(:allow_blank)
126
+
127
+ begin
128
+ @existing.update_attribute(@attribute, '')
129
+ rescue ::ActiveRecord::StatementInvalid => e
130
+ raise ScriptError, "You declared that #{@attribute} accepts blank values in validate_uniqueness_of, " <<
131
+ "but I cannot save blank values in the database, got: #{e.message}" if @options[:allow_blank]
132
+ return true
133
+ end
134
+
135
+ super
136
+ end
137
+
138
+ # Returns a value to be used as new scope. It deals with four different
139
+ # cases: date, time, boolean and stringfiable (everything that can be
140
+ # converted to a string and the next value makes sense)
141
+ #
142
+ def new_value_for_scope(scope)
143
+ column_type = if @existing.respond_to?(:column_for_attribute)
144
+ @existing.column_for_attribute(scope)
145
+ else
146
+ nil
147
+ end
148
+
149
+ case column_type.type
150
+ when :int, :integer, :float, :decimal
151
+ new_value_for_stringfiable_scope(scope)
152
+ when :datetime, :timestamp, :time
153
+ Time.now + 10000
154
+ when :date
155
+ Date.today + 100
156
+ when :boolean
157
+ !@existing.send(scope)
158
+ else
159
+ new_value_for_stringfiable_scope(scope)
160
+ end
161
+ end
162
+
163
+ # Returns a value to be used as scope by generating a range of values
164
+ # and searching for them in the database.
165
+ #
166
+ def new_value_for_stringfiable_scope(scope)
167
+ values = [(@existing.send(scope) || 999).next.to_s]
168
+
169
+ # Generate a range of values to search in the database
170
+ 100.times do
171
+ values << values.last.next
172
+ end
173
+ conditions = { scope => values, @attribute => @value }
174
+
175
+ # Get values from the database, get the scope attribute and map them to string.
176
+ db_values = subject_class.find(:all, :conditions => conditions, :select => scope)
177
+ db_values.map!{ |r| r.send(scope).to_s }
178
+
179
+ if value_to_return = (values - db_values).first
180
+ value_to_return
181
+ else
182
+ raise ScriptError, "Tried to find an unique scope value for #{scope} but I could not. " <<
183
+ "The conditions hash was #{conditions.inspect} and it returned all records."
184
+ end
185
+ end
186
+ end
187
+
188
+ # Ensures that the model cannot be saved if one of the attributes listed
189
+ # is not unique.
190
+ #
191
+ # Requires an existing record in the database. If you supply :allow_nil as
192
+ # option, you need to have in the database a record which is not nil in the
193
+ # given attributes. The same is required for allow_blank option.
194
+ #
195
+ # Notice that the record being validate should not be the same as in the
196
+ # database. In other words, you can't do this:
197
+ #
198
+ # subject { Post.create!(@valid_attributes) }
199
+ # should_validate_uniqueness_of :title
200
+ #
201
+ # But don't worry, if you eventually do that, a helpful error message
202
+ # will be raised.
203
+ #
204
+ # == Options
205
+ #
206
+ # * <tt>:scope</tt> - field(s) to scope the uniqueness to.
207
+ # * <tt>:case_sensitive</tt> - the matcher look for an exact match.
208
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
209
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
210
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
211
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
212
+ #
213
+ # == Examples
214
+ #
215
+ # it { should validate_uniqueness_of(:keyword, :username) }
216
+ # it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
217
+ # it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
218
+ #
219
+ # should_validate_uniqueness_of :keyword, :username
220
+ # should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
221
+ # should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
222
+ #
223
+ # should_validate_uniqueness_of :email do |m|
224
+ # m.scope = name
225
+ # m.case_sensitive = false
226
+ # end
227
+ #
228
+ def validate_uniqueness_of(*attributes, &block)
229
+ ValidateUniquenessOfMatcher.new(*attributes, &block).spec(self)
230
+ end
231
+ end
232
+ end
233
+ end