activerecord 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (68) hide show
  1. data/CHANGELOG +250 -0
  2. data/README +17 -9
  3. data/dev-utils/eval_debugger.rb +1 -1
  4. data/install.rb +3 -1
  5. data/lib/active_record.rb +9 -2
  6. data/lib/active_record/acts/list.rb +178 -0
  7. data/lib/active_record/acts/tree.rb +44 -0
  8. data/lib/active_record/associations.rb +45 -8
  9. data/lib/active_record/associations/association_collection.rb +18 -9
  10. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +14 -13
  11. data/lib/active_record/associations/has_many_association.rb +21 -12
  12. data/lib/active_record/base.rb +137 -37
  13. data/lib/active_record/callbacks.rb +30 -25
  14. data/lib/active_record/connection_adapters/abstract_adapter.rb +57 -33
  15. data/lib/active_record/connection_adapters/mysql_adapter.rb +4 -0
  16. data/lib/active_record/connection_adapters/sqlite_adapter.rb +3 -2
  17. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +298 -0
  18. data/lib/active_record/fixtures.rb +241 -147
  19. data/lib/active_record/support/class_inheritable_attributes.rb +5 -2
  20. data/lib/active_record/support/inflector.rb +13 -12
  21. data/lib/active_record/support/misc.rb +6 -0
  22. data/lib/active_record/timestamp.rb +33 -0
  23. data/lib/active_record/transactions.rb +1 -1
  24. data/lib/active_record/validations.rb +294 -16
  25. data/rakefile +3 -7
  26. data/test/abstract_unit.rb +1 -4
  27. data/test/associations_test.rb +17 -4
  28. data/test/base_test.rb +37 -5
  29. data/test/connections/native_sqlserver/connection.rb +15 -0
  30. data/test/deprecated_associations_test.rb +40 -38
  31. data/test/finder_test.rb +82 -4
  32. data/test/fixtures/accounts.yml +8 -0
  33. data/test/fixtures/company.rb +6 -0
  34. data/test/fixtures/company_in_module.rb +1 -1
  35. data/test/fixtures/db_definitions/mysql.sql +13 -0
  36. data/test/fixtures/db_definitions/postgresql.sql +13 -0
  37. data/test/fixtures/db_definitions/sqlite.sql +14 -0
  38. data/test/fixtures/db_definitions/sqlserver.sql +110 -0
  39. data/test/fixtures/db_definitions/sqlserver2.sql +4 -0
  40. data/test/fixtures/developer.rb +2 -2
  41. data/test/fixtures/developers.yml +13 -0
  42. data/test/fixtures/fixture_database.sqlite +0 -0
  43. data/test/fixtures/fixture_database_2.sqlite +0 -0
  44. data/test/fixtures/mixin.rb +17 -0
  45. data/test/fixtures/mixins.yml +14 -0
  46. data/test/fixtures/naked/csv/accounts.csv +1 -0
  47. data/test/fixtures/naked/yml/accounts.yml +1 -0
  48. data/test/fixtures/naked/yml/companies.yml +1 -0
  49. data/test/fixtures/naked/yml/courses.yml +1 -0
  50. data/test/fixtures/project.rb +6 -0
  51. data/test/fixtures/reply.rb +14 -1
  52. data/test/fixtures/topic.rb +2 -2
  53. data/test/fixtures/topics/first +1 -0
  54. data/test/fixtures_test.rb +42 -12
  55. data/test/inflector_test.rb +2 -1
  56. data/test/inheritance_test.rb +22 -12
  57. data/test/mixin_test.rb +138 -0
  58. data/test/pk_test.rb +4 -2
  59. data/test/reflection_test.rb +3 -3
  60. data/test/transactions_test.rb +15 -0
  61. data/test/validations_test.rb +229 -4
  62. metadata +24 -10
  63. data/lib/active_record/associations.rb.orig +0 -555
  64. data/test/deprecated_associations_test.rb.orig +0 -334
  65. data/test/fixtures/accounts/signals37 +0 -3
  66. data/test/fixtures/accounts/unknown +0 -2
  67. data/test/fixtures/developers/david +0 -2
  68. data/test/fixtures/developers/jamis +0 -2
@@ -28,10 +28,13 @@ module ClassInheritableAttributes # :nodoc:
28
28
  inheritable_attributes[key]
29
29
  end
30
30
 
31
+ def reset_inheritable_attributes
32
+ inheritable_attributes.clear
33
+ end
34
+
31
35
  private
32
36
  def inherited(child)
33
37
  @@classes[child] = inheritable_attributes.dup
34
- end
35
-
38
+ end
36
39
  end
37
40
  end
@@ -62,17 +62,18 @@ module Inflector
62
62
 
63
63
  def singular_rules #:doc:
64
64
  [
65
- [/(x|ch|ss)es$/, '\1'],
66
- [/([^aeiouy]|qu)ies$/, '\1y'],
67
- [/([lr])ves$/, '\1f'],
68
- [/([^f])ves$/, '\1fe'],
69
- [/(analy|ba|diagno|parenthe|progno|synop|the)ses$/, '\1sis'],
70
- [/([ti])a$/, '\1um'],
71
- [/people$/, 'person'],
72
- [/men$/, 'man'],
73
- [/status$/, 'status'],
74
- [/children$/, 'child'],
75
- [/s$/, '']
76
- ]
65
+ [/(x|ch|ss)es$/, '\1'],
66
+ [/movies$/, 'movie'],
67
+ [/([^aeiouy]|qu)ies$/, '\1y'],
68
+ [/([lr])ves$/, '\1f'],
69
+ [/([^f])ves$/, '\1fe'],
70
+ [/(analy|ba|diagno|parenthe|progno|synop|the)ses$/, '\1sis'],
71
+ [/([ti])a$/, '\1um'],
72
+ [/people$/, 'person'],
73
+ [/men$/, 'man'],
74
+ [/status$/, 'status'],
75
+ [/children$/, 'child'],
76
+ [/s$/, '']
77
+ ]
77
78
  end
78
79
  end
@@ -0,0 +1,6 @@
1
+ def silence_warnings
2
+ old_verbose, $VERBOSE = $VERBOSE, nil
3
+ result = yield
4
+ $VERBOSE = old_verbose
5
+ return result
6
+ end
@@ -0,0 +1,33 @@
1
+ module ActiveRecord
2
+ # Active Records will automatically record creation and/or update timestamps of database objects
3
+ # if fields of the names created_at/created_on or updated_at/updated_on are present. This module is
4
+ # automatically included, so you don't need to do that manually.
5
+ #
6
+ # This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
7
+ module Timestamp
8
+ def self.append_features(base) # :nodoc:
9
+ super
10
+ base.before_create :timestamp_before_create
11
+ base.before_update :timestamp_before_update
12
+ end
13
+
14
+ def timestamp_before_create
15
+ write_attribute("created_at", Time.now) if record_timestamps && respond_to?(:created_at) && created_at.nil?
16
+ write_attribute("created_on", Time.now) if record_timestamps && respond_to?(:created_on) && created_on.nil?
17
+ timestamp_before_update
18
+ end
19
+
20
+ def timestamp_before_update
21
+ write_attribute("updated_at", Time.now) if record_timestamps && respond_to?(:updated_at)
22
+ write_attribute("updated_on", Time.now) if record_timestamps && respond_to?(:updated_on)
23
+ end
24
+ end
25
+
26
+ class Base
27
+ # Records the creation date and possibly time in created_on (date only) or created_at (date and time) and the update date and possibly
28
+ # time in updated_on and updated_at. This only happens if the object responds to either of these messages, which they will do automatically
29
+ # if the table has columns of either of these names. This feature is turned on by default.
30
+ @@record_timestamps = true
31
+ cattr_accessor :record_timestamps
32
+ end
33
+ end
@@ -116,4 +116,4 @@ module ActiveRecord
116
116
  end
117
117
  end
118
118
  end
119
- end
119
+ end
@@ -36,7 +36,11 @@ module ActiveRecord
36
36
  # person.save # => true (and person is now saved in the database)
37
37
  #
38
38
  # An +Errors+ object is automatically created for every Active Record.
39
+ #
40
+ # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
39
41
  module Validations
42
+ VALIDATIONS = %w( validate validate_on_create validate_on_update )
43
+
40
44
  def self.append_features(base) # :nodoc:
41
45
  super
42
46
 
@@ -46,7 +50,224 @@ module ActiveRecord
46
50
 
47
51
  alias_method :update_attribute_without_validation_skipping, :update_attribute
48
52
  alias_method :update_attribute, :update_attribute_with_validation_skipping
53
+
54
+ VALIDATIONS.each { |vd| base.class_eval("def self.#{vd}(*methods) write_inheritable_array(\"#{vd}\", methods - (read_inheritable_attribute(\"#{vd}\") || [])) end") }
49
55
  end
56
+
57
+ base.extend(ClassMethods)
58
+ end
59
+
60
+ # All of the following validations are defined in the class scope of the model that you're interested in validating.
61
+ # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
62
+ # these over the low-level calls to validate and validate_on_create when possible.
63
+ module ClassMethods
64
+ # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
65
+ #
66
+ # Model:
67
+ # class Person < ActiveRecord::Base
68
+ # validates_confirmation_of :user_name, :password
69
+ # validates_confirmation_of :email_address, :message => "should match confirmation"
70
+ # end
71
+ #
72
+ # View:
73
+ # <%= password_field "person", "password" %>
74
+ # <%= password_field "person", "password_confirmation" %>
75
+ #
76
+ # The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
77
+ # It exists only as an in-memory variable for validating the password. This check is performed both on create and update.
78
+ #
79
+ # Configuration options:
80
+ # * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation")
81
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
82
+ def validates_confirmation_of(*attr_names)
83
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
84
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
85
+
86
+ for attr_name in attr_names
87
+ attr_accessor "#{attr_name}_confirmation"
88
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add('#{attr_name}', "#{configuration[:message]}") unless #{attr_name} == #{attr_name}_confirmation}))
89
+ end
90
+ end
91
+
92
+ # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
93
+ #
94
+ # class Person < ActiveRecord::Base
95
+ # validates_acceptance_of :terms_of_service
96
+ # validates_acceptance_of :eula, :message => "must be abided"
97
+ # end
98
+ #
99
+ # The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update.
100
+ #
101
+ # Configuration options:
102
+ # * <tt>message</tt> - A custom error message (default is: "must be accepted")
103
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
104
+ #
105
+ # NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
106
+ def validates_acceptance_of(*attr_names)
107
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save }
108
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
109
+
110
+ for attr_name in attr_names
111
+ attr_accessor(attr_name)
112
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add('#{attr_name}', '#{configuration[:message]}') unless #{attr_name} == "1"}))
113
+ end
114
+ end
115
+
116
+ # Validates that the specified attributes are neither nil nor empty. Happens by default on both create and update.
117
+ #
118
+ # Configuration options:
119
+ # * <tt>message</tt> - A custom error message (default is: "has already been taken")
120
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
121
+ def validates_presence_of(*attr_names)
122
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:empty], :on => :save }
123
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
124
+
125
+ for attr_name in attr_names
126
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add_on_empty('#{attr_name}', "#{configuration[:message]}")}))
127
+ end
128
+ end
129
+
130
+ # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
131
+ #
132
+ # class Person < ActiveRecord::Base
133
+ # validates_length_of :first_name, :maximum=>30
134
+ # validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
135
+ # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
136
+ # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
137
+ # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
138
+ # end
139
+ #
140
+ # Configuration options:
141
+ # * <tt>minimum</tt> - The minimum size of the attribute
142
+ # * <tt>maximum</tt> - The maximum size of the attribute
143
+ # * <tt>is</tt> - The exact size of the attribute
144
+ # * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
145
+ # * <tt>in</tt> - A synonym(or alias) for :within
146
+ #
147
+ # * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (max is %d characters)")
148
+ # * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
149
+ # * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
150
+ # * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message
151
+ # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
152
+ def validates_length_of(*attr_names)
153
+ configuration = { :too_long => ActiveRecord::Errors.default_error_messages[:too_long], :too_short => ActiveRecord::Errors.default_error_messages[:too_short], :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length], :on => :save }
154
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
155
+
156
+ # you must use one of 4 options, :within, :maximum, :minimum, or :is
157
+ within = configuration[:within] || configuration[:in]
158
+ maximum = configuration[:maximum]
159
+ minimum = configuration[:minimum]
160
+ is = configuration[:is]
161
+
162
+ raise(ArgumentError, "The :within, :maximum, :minimum, or :is options must be passed in the configuration hash") unless within or maximum or minimum or is
163
+ # but not more than 1 of them at a time
164
+ options_used = 0
165
+ options_used += 1 if within
166
+ options_used += 1 if maximum
167
+ options_used += 1 if minimum
168
+ options_used += 1 if is
169
+ raise(ArgumentError, "The :within, :maximum, :minimum, and :is options are mutually exclusive") if options_used > 1
170
+
171
+ option_to_use = within || maximum || minimum || is
172
+ for attr_name in attr_names
173
+ if within
174
+ raise(ArgumentError, "The :within option must be a Range") unless within.kind_of?(Range)
175
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add_on_boundary_breaking('#{attr_name}', #{within}, "#{configuration[:too_long]}", "#{configuration[:too_short]}")}))
176
+ elsif maximum
177
+ raise(ArgumentError, "The :maximum option must be a Fixnum") unless maximum.kind_of?(Fixnum)
178
+ msg = configuration[:message] || configuration[:too_long]
179
+ msg = (msg % maximum) rescue msg
180
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add( '#{attr_name}', '#{msg}') if #{attr_name}.to_s.length > #{maximum} }))
181
+ elsif minimum
182
+ raise(ArgumentError, "The :minimum option must be a Fixnum") unless minimum.kind_of?(Fixnum)
183
+ msg = configuration[:message] || configuration[:too_short]
184
+ msg = (msg % minimum) rescue msg
185
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add( '#{attr_name}', '#{msg}') if #{attr_name}.to_s.length < #{minimum} }))
186
+ else
187
+ raise(ArgumentError, "The :is option must be a Fixnum") unless is.kind_of?(Fixnum)
188
+ msg = configuration[:message] || configuration[:wrong_length]
189
+ msg = (msg % is) rescue msg
190
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add( '#{attr_name}', '#{msg}') if #{attr_name}.to_s.length != #{is} }))
191
+ end
192
+ end
193
+ end
194
+
195
+ # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
196
+ # can be named "davidhh".
197
+ #
198
+ # class Person < ActiveRecord::Base
199
+ # validates_uniqueness_of :user_name
200
+ # end
201
+ #
202
+ # When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
203
+ # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
204
+ #
205
+ # Configuration options:
206
+ # * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
207
+ def validates_uniqueness_of(*attr_names)
208
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
209
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
210
+
211
+ for attr_name in attr_names
212
+ class_eval(%(validate %{errors.add("#{attr_name}", "#{configuration[:message]}") if self.class.find_first(new_record? ? ["#{attr_name} = ?", #{attr_name}] : ["#{attr_name} = ? AND id <> ?", #{attr_name}, id])}))
213
+ end
214
+ end
215
+
216
+ # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
217
+ # provided.
218
+ #
219
+ # class Person < ActiveRecord::Base
220
+ # validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :on => :create
221
+ # end
222
+ #
223
+ # A regular expression must be provided or else an exception will be raised.
224
+ #
225
+ # Configuration options:
226
+ # * <tt>message</tt> - A custom error message (default is: "is invalid")
227
+ # * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!)
228
+ # * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
229
+ def validates_format_of(*attr_names)
230
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
231
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
232
+
233
+ raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
234
+
235
+ for attr_name in attr_names
236
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add("#{attr_name}", "#{configuration[:message]}") unless #{attr_name} and #{attr_name}.to_s.match(/#{configuration[:with]}/)}))
237
+ end
238
+ end
239
+
240
+ # Validates whether the value of the specified attribute is available in a particular enumerable object.
241
+ #
242
+ # class Person < ActiveRecord::Base
243
+ # validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
244
+ # validates_inclusion_of :age, :in=>0..99
245
+ # end
246
+ #
247
+ # Configuration options:
248
+ # * <tt>in</tt> - An enumerable object of available items
249
+ # * <tt>message</tt> - Specifieds a customer error message (default is: "is not included in the list")
250
+ def validates_inclusion_of(*attr_names)
251
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
252
+ configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
253
+ enum = configuration[:in] || configuration[:within]
254
+
255
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
256
+
257
+ for attr_name in attr_names
258
+ class_eval(%(#{validation_method(configuration[:on])} %{errors.add("#{attr_name}", "#{configuration[:message]}") unless (#{enum.inspect}).include?(#{attr_name}) }))
259
+ end
260
+ end
261
+
262
+
263
+ private
264
+ def validation_method(on)
265
+ case on
266
+ when :save then :validate
267
+ when :create then :validate_on_create
268
+ when :update then :validate_on_update
269
+ end
270
+ end
50
271
  end
51
272
 
52
273
  # The validation process on save can be skipped by passing false. The regular Base#save method is
@@ -66,8 +287,18 @@ module ActiveRecord
66
287
  # Runs validate and validate_on_create or validate_on_update and returns true if no errors were added otherwise false.
67
288
  def valid?
68
289
  errors.clear
290
+
291
+ run_validations(:validate)
69
292
  validate
70
- if new_record? then validate_on_create else validate_on_update end
293
+
294
+ if new_record?
295
+ run_validations(:validate_on_create)
296
+ validate_on_create
297
+ else
298
+ run_validations(:validate_on_update)
299
+ validate_on_update
300
+ end
301
+
71
302
  errors.empty?
72
303
  end
73
304
 
@@ -89,6 +320,37 @@ module ActiveRecord
89
320
  # Overwrite this method for validation checks used only on updates.
90
321
  def validate_on_update # :doc:
91
322
  end
323
+
324
+ private
325
+ def run_validations(validation_method)
326
+ validations = self.class.read_inheritable_attribute(validation_method.to_s)
327
+ if validations.nil? then return end
328
+ validations.each do |validation|
329
+ if Symbol === validation
330
+ self.send(validation)
331
+ elsif String === validation
332
+ eval(validation, binding)
333
+ elsif validation_block?(validation)
334
+ validation.call(self)
335
+ elsif filter_class?(validation, validation_method)
336
+ validation.send(validation_method, self)
337
+ else
338
+ raise(
339
+ ActiveRecordError,
340
+ "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
341
+ "class implementing a static validation method"
342
+ )
343
+ end
344
+ end
345
+ end
346
+
347
+ def validation_block?(validation)
348
+ validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1)
349
+ end
350
+
351
+ def validation_class?(validation, validation_method)
352
+ validation.respond_to?(validation_method)
353
+ end
92
354
  end
93
355
 
94
356
  # Active Record validation is reported to and from this object, which is used by Base#save to
@@ -98,6 +360,20 @@ module ActiveRecord
98
360
  @base, @errors = base, {}
99
361
  end
100
362
 
363
+ @@default_error_messages = {
364
+ :inclusion => "is not included in the list",
365
+ :invalid => "is invalid",
366
+ :confirmation => "doesn't match confirmation",
367
+ :accepted => "must be accepted",
368
+ :empty => "can't be empty",
369
+ :too_long => "is too long (max is %d characters)",
370
+ :too_short => "is too short (min is %d characters)",
371
+ :wrong_length => "is the wrong length (should be %d characters)",
372
+ :taken => "has already been taken",
373
+ }
374
+ cattr_accessor :default_error_messages
375
+
376
+
101
377
  # Adds an error to the base object instead of any particular attribute. This is used
102
378
  # to report errors that doesn't tie to any specific attribute, but rather to the object
103
379
  # as a whole. These error messages doesn't get prepended with any field name when iterating
@@ -110,42 +386,42 @@ module ActiveRecord
110
386
  # for the same attribute and ensure that this error object returns false when asked if +empty?+. More than one
111
387
  # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
112
388
  # If no +msg+ is supplied, "invalid" is assumed.
113
- def add(attribute, msg = "invalid")
114
- @errors[attribute] = [] if @errors[attribute].nil?
115
- @errors[attribute] << msg
389
+ def add(attribute, msg = @@default_error_messages[:invalid])
390
+ @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
391
+ @errors[attribute.to_s] << msg
116
392
  end
117
393
 
118
394
  # Will add an error message to each of the attributes in +attributes+ that is empty (defined by <tt>attribute_present?</tt>).
119
- def add_on_empty(attributes, msg = "can't be empty")
120
- [attributes].flatten.each { |attr| add(attr, msg) unless @base.attribute_present?(attr) }
395
+ def add_on_empty(attributes, msg = @@default_error_messages[:empty])
396
+ [attributes].flatten.each { |attr| add(attr, msg) unless @base.attribute_present?(attr.to_s) }
121
397
  end
122
398
 
123
399
  # Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
124
400
  # If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
125
- def add_on_boundary_breaking(attributes, range, too_long_msg = "is too long (max is %d characters)", too_short_msg = "is too short (min is %d characters)")
401
+ def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
126
402
  for attr in [attributes].flatten
127
- add(attr, too_short_msg % range.begin) if @base.attribute_present?(attr) && @base.send(attr).length < range.begin
128
- add(attr, too_long_msg % range.end) if @base.attribute_present?(attr) && @base.send(attr).length > range.end
403
+ add(attr, too_short_msg % range.begin) if @base[attr.to_s] && @base.send(attr.to_s).length < range.begin
404
+ add(attr, too_long_msg % range.end) if @base[attr.to_s] && @base.send(attr.to_s).length > range.end
129
405
  end
130
406
  end
131
407
 
132
- alias :add_on_boundry_breaking :add_on_boundary_breaking
408
+ alias :add_on_boundary_breaking :add_on_boundary_breaking
133
409
 
134
410
  # Returns true if the specified +attribute+ has errors associated with it.
135
411
  def invalid?(attribute)
136
- !@errors[attribute].nil?
412
+ !@errors[attribute.to_s].nil?
137
413
  end
138
414
 
139
415
  # * Returns nil, if no errors are associated with the specified +attribute+.
140
416
  # * Returns the error message, if one error is associated with the specified +attribute+.
141
417
  # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
142
418
  def on(attribute)
143
- if @errors[attribute].nil?
419
+ if @errors[attribute.to_s].nil?
144
420
  nil
145
- elsif @errors[attribute].length == 1
146
- @errors[attribute].first
421
+ elsif @errors[attribute.to_s].length == 1
422
+ @errors[attribute.to_s].first
147
423
  else
148
- @errors[attribute]
424
+ @errors[attribute.to_s]
149
425
  end
150
426
  end
151
427
 
@@ -173,7 +449,9 @@ module ActiveRecord
173
449
 
174
450
  @errors.each_key do |attr|
175
451
  @errors[attr].each do |msg|
176
- if attr == :base
452
+ next if msg.nil?
453
+
454
+ if attr == "base"
177
455
  full_messages << msg
178
456
  else
179
457
  full_messages << @base.class.human_attribute_name(attr) + " " + msg