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,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