activerecord 2.1.2 → 2.2.2
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.
- data/CHANGELOG +32 -6
- data/README +0 -0
- data/Rakefile +4 -5
- data/lib/active_record.rb +11 -10
- data/lib/active_record/aggregations.rb +110 -38
- data/lib/active_record/association_preload.rb +104 -15
- data/lib/active_record/associations.rb +427 -212
- data/lib/active_record/associations/association_collection.rb +101 -16
- data/lib/active_record/associations/association_proxy.rb +65 -13
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +13 -3
- data/lib/active_record/associations/has_many_association.rb +28 -28
- data/lib/active_record/associations/has_many_through_association.rb +21 -19
- data/lib/active_record/associations/has_one_association.rb +24 -7
- data/lib/active_record/associations/has_one_through_association.rb +3 -4
- data/lib/active_record/attribute_methods.rb +13 -5
- data/lib/active_record/base.rb +435 -212
- data/lib/active_record/calculations.rb +12 -5
- data/lib/active_record/callbacks.rb +28 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +355 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +42 -215
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -5
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +48 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +10 -4
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -26
- data/lib/active_record/connection_adapters/mysql_adapter.rb +71 -45
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +155 -84
- data/lib/active_record/dirty.rb +25 -7
- data/lib/active_record/dynamic_finder_match.rb +41 -0
- data/lib/active_record/fixtures.rb +10 -9
- data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
- data/lib/active_record/locale/en.yml +54 -0
- data/lib/active_record/migration.rb +47 -10
- data/lib/active_record/named_scope.rb +29 -16
- data/lib/active_record/reflection.rb +118 -54
- data/lib/active_record/schema_dumper.rb +13 -7
- data/lib/active_record/test_case.rb +18 -5
- data/lib/active_record/transactions.rb +89 -34
- data/lib/active_record/validations.rb +270 -180
- data/lib/active_record/version.rb +1 -1
- data/test/cases/active_schema_test_mysql.rb +5 -0
- data/test/cases/adapter_test.rb +6 -0
- data/test/cases/aggregations_test.rb +39 -0
- data/test/cases/associations/belongs_to_associations_test.rb +10 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +30 -12
- data/test/cases/associations/eager_test.rb +54 -5
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +77 -10
- data/test/cases/associations/has_many_associations_test.rb +74 -7
- data/test/cases/associations/has_many_through_associations_test.rb +50 -3
- data/test/cases/associations/has_one_associations_test.rb +17 -0
- data/test/cases/associations/has_one_through_associations_test.rb +49 -1
- data/test/cases/associations_test.rb +0 -0
- data/test/cases/attribute_methods_test.rb +59 -4
- data/test/cases/base_test.rb +93 -21
- data/test/cases/binary_test.rb +1 -5
- data/test/cases/calculations_test.rb +5 -0
- data/test/cases/callbacks_observers_test.rb +38 -0
- data/test/cases/connection_test_mysql.rb +1 -1
- data/test/cases/defaults_test.rb +32 -1
- data/test/cases/deprecated_finder_test.rb +0 -0
- data/test/cases/dirty_test.rb +13 -0
- data/test/cases/finder_test.rb +162 -12
- data/test/cases/fixtures_test.rb +32 -3
- data/test/cases/helper.rb +15 -0
- data/test/cases/i18n_test.rb +41 -0
- data/test/cases/inheritance_test.rb +2 -2
- data/test/cases/lifecycle_test.rb +0 -0
- data/test/cases/locking_test.rb +4 -9
- data/test/cases/method_scoping_test.rb +109 -2
- data/test/cases/migration_test.rb +43 -8
- data/test/cases/multiple_db_test.rb +25 -0
- data/test/cases/named_scope_test.rb +74 -0
- data/test/cases/pooled_connections_test.rb +103 -0
- data/test/cases/readonly_test.rb +0 -0
- data/test/cases/reflection_test.rb +11 -3
- data/test/cases/reload_models_test.rb +20 -0
- data/test/cases/sanitize_test.rb +25 -0
- data/test/cases/schema_authorization_test_postgresql.rb +2 -2
- data/test/cases/transactions_test.rb +62 -12
- data/test/cases/unconnected_test.rb +0 -0
- data/test/cases/validations_i18n_test.rb +921 -0
- data/test/cases/validations_test.rb +44 -33
- data/test/connections/native_mysql/connection.rb +1 -3
- data/test/fixtures/companies.yml +1 -0
- data/test/fixtures/customers.yml +10 -1
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/organizations.yml +5 -0
- data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
- data/test/models/author.rb +3 -0
- data/test/models/category.rb +3 -0
- data/test/models/club.rb +6 -0
- data/test/models/company.rb +25 -1
- data/test/models/customer.rb +19 -1
- data/test/models/member.rb +2 -0
- data/test/models/member_detail.rb +4 -0
- data/test/models/organization.rb +4 -0
- data/test/models/parrot.rb +1 -0
- data/test/models/post.rb +3 -0
- data/test/models/reply.rb +0 -0
- data/test/models/topic.rb +3 -0
- data/test/schema/schema.rb +12 -1
- metadata +22 -10
- data/lib/active_record/vendor/mysql.rb +0 -1214
- data/test/cases/adapter_test_sqlserver.rb +0 -95
- data/test/cases/table_name_test_sqlserver.rb +0 -23
- data/test/cases/threaded_connections_test.rb +0 -48
- data/test/schema/sqlserver_specific_schema.rb +0 -5
@@ -1,5 +1,5 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
# Raised by save
|
2
|
+
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
|
3
3
|
# +record+ method to retrieve the record which did not validate.
|
4
4
|
# begin
|
5
5
|
# complex_operation_that_calls_save!_internally
|
@@ -18,70 +18,97 @@ module ActiveRecord
|
|
18
18
|
# determine whether the object is in a valid state to be saved. See usage example in Validations.
|
19
19
|
class Errors
|
20
20
|
include Enumerable
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def default_error_messages
|
24
|
+
ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages').")
|
25
|
+
I18n.translate 'activerecord.errors.messages'
|
26
|
+
end
|
27
|
+
end
|
21
28
|
|
22
29
|
def initialize(base) # :nodoc:
|
23
30
|
@base, @errors = base, {}
|
24
31
|
end
|
25
32
|
|
26
|
-
@@default_error_messages = {
|
27
|
-
:inclusion => "is not included in the list",
|
28
|
-
:exclusion => "is reserved",
|
29
|
-
:invalid => "is invalid",
|
30
|
-
:confirmation => "doesn't match confirmation",
|
31
|
-
:accepted => "must be accepted",
|
32
|
-
:empty => "can't be empty",
|
33
|
-
:blank => "can't be blank",
|
34
|
-
:too_long => "is too long (maximum is %d characters)",
|
35
|
-
:too_short => "is too short (minimum is %d characters)",
|
36
|
-
:wrong_length => "is the wrong length (should be %d characters)",
|
37
|
-
:taken => "has already been taken",
|
38
|
-
:not_a_number => "is not a number",
|
39
|
-
:greater_than => "must be greater than %d",
|
40
|
-
:greater_than_or_equal_to => "must be greater than or equal to %d",
|
41
|
-
:equal_to => "must be equal to %d",
|
42
|
-
:less_than => "must be less than %d",
|
43
|
-
:less_than_or_equal_to => "must be less than or equal to %d",
|
44
|
-
:odd => "must be odd",
|
45
|
-
:even => "must be even"
|
46
|
-
}
|
47
|
-
|
48
|
-
# Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
|
49
|
-
cattr_accessor :default_error_messages
|
50
|
-
|
51
|
-
|
52
33
|
# Adds an error to the base object instead of any particular attribute. This is used
|
53
34
|
# to report errors that don't tie to any specific attribute, but rather to the object
|
54
35
|
# as a whole. These error messages don't get prepended with any field name when iterating
|
55
|
-
# with each_full
|
36
|
+
# with +each_full+, so they should be complete sentences.
|
56
37
|
def add_to_base(msg)
|
57
38
|
add(:base, msg)
|
58
39
|
end
|
59
40
|
|
60
|
-
# Adds an error message (+
|
41
|
+
# Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
|
61
42
|
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
|
62
43
|
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
63
|
-
# If no +
|
64
|
-
|
65
|
-
|
66
|
-
|
44
|
+
# If no +messsage+ is supplied, :invalid is assumed.
|
45
|
+
# If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
|
46
|
+
def add(attribute, message = nil, options = {})
|
47
|
+
message ||= :invalid
|
48
|
+
message = generate_message(attribute, message, options) if message.is_a?(Symbol)
|
49
|
+
@errors[attribute.to_s] ||= []
|
50
|
+
@errors[attribute.to_s] << message
|
67
51
|
end
|
68
52
|
|
69
53
|
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
70
|
-
def add_on_empty(attributes,
|
54
|
+
def add_on_empty(attributes, custom_message = nil)
|
71
55
|
for attr in [attributes].flatten
|
72
56
|
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
73
|
-
is_empty = value.respond_to?(
|
74
|
-
add(attr,
|
57
|
+
is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
58
|
+
add(attr, :empty, :default => custom_message) unless !value.nil? && !is_empty
|
75
59
|
end
|
76
60
|
end
|
77
61
|
|
78
62
|
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
|
79
|
-
def add_on_blank(attributes,
|
63
|
+
def add_on_blank(attributes, custom_message = nil)
|
80
64
|
for attr in [attributes].flatten
|
81
65
|
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
82
|
-
add(attr,
|
66
|
+
add(attr, :blank, :default => custom_message) if value.blank?
|
83
67
|
end
|
84
68
|
end
|
69
|
+
|
70
|
+
# Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
|
71
|
+
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
|
72
|
+
# it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
|
73
|
+
# default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
|
74
|
+
# translated attribute name and the value are available for interpolation.
|
75
|
+
#
|
76
|
+
# When using inheritence in your models, it will check all the inherited models too, but only if the model itself
|
77
|
+
# hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
|
78
|
+
# error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
|
79
|
+
#
|
80
|
+
# <ol>
|
81
|
+
# <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
|
82
|
+
# <li><tt>activerecord.errors.models.admin.blank</tt></li>
|
83
|
+
# <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
|
84
|
+
# <li><tt>activerecord.errors.models.user.blank</tt></li>
|
85
|
+
# <li><tt>activerecord.errors.messages.blank</tt></li>
|
86
|
+
# <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
|
87
|
+
# </ol>
|
88
|
+
def generate_message(attribute, message = :invalid, options = {})
|
89
|
+
|
90
|
+
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
|
91
|
+
|
92
|
+
defaults = @base.class.self_and_descendents_from_active_record.map do |klass|
|
93
|
+
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
94
|
+
:"models.#{klass.name.underscore}.#{message}" ]
|
95
|
+
end
|
96
|
+
|
97
|
+
defaults << options.delete(:default)
|
98
|
+
defaults = defaults.compact.flatten << :"messages.#{message}"
|
99
|
+
|
100
|
+
key = defaults.shift
|
101
|
+
value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
|
102
|
+
|
103
|
+
options = { :default => defaults,
|
104
|
+
:model => @base.class.human_name,
|
105
|
+
:attribute => @base.class.human_attribute_name(attribute.to_s),
|
106
|
+
:value => value,
|
107
|
+
:scope => [:activerecord, :errors]
|
108
|
+
}.merge(options)
|
109
|
+
|
110
|
+
I18n.translate(key, options)
|
111
|
+
end
|
85
112
|
|
86
113
|
# Returns true if the specified +attribute+ has errors associated with it.
|
87
114
|
#
|
@@ -97,7 +124,7 @@ module ActiveRecord
|
|
97
124
|
!@errors[attribute.to_s].nil?
|
98
125
|
end
|
99
126
|
|
100
|
-
# Returns nil
|
127
|
+
# Returns +nil+, if no errors are associated with the specified +attribute+.
|
101
128
|
# Returns the error message, if one error is associated with the specified +attribute+.
|
102
129
|
# Returns an array of error messages, if more than one error is associated with the specified +attribute+.
|
103
130
|
#
|
@@ -118,7 +145,7 @@ module ActiveRecord
|
|
118
145
|
|
119
146
|
alias :[] :on
|
120
147
|
|
121
|
-
# Returns errors assigned to the base object through add_to_base according to the normal rules of on(attribute)
|
148
|
+
# Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
|
122
149
|
def on_base
|
123
150
|
on(:base)
|
124
151
|
end
|
@@ -131,15 +158,15 @@ module ActiveRecord
|
|
131
158
|
# end
|
132
159
|
#
|
133
160
|
# company = Company.create(:address => '123 First St.')
|
134
|
-
# company.errors.each{|attr,msg| puts "#{attr} - #{msg}" }
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
161
|
+
# company.errors.each{|attr,msg| puts "#{attr} - #{msg}" }
|
162
|
+
# # => name - is too short (minimum is 5 characters)
|
163
|
+
# # name - can't be blank
|
164
|
+
# # address - can't be blank
|
138
165
|
def each
|
139
166
|
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
140
167
|
end
|
141
168
|
|
142
|
-
# Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
|
169
|
+
# Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
|
143
170
|
# through iteration as "First name can't be empty".
|
144
171
|
#
|
145
172
|
# class Company < ActiveRecord::Base
|
@@ -148,10 +175,10 @@ module ActiveRecord
|
|
148
175
|
# end
|
149
176
|
#
|
150
177
|
# company = Company.create(:address => '123 First St.')
|
151
|
-
# company.errors.each_full{|msg| puts msg }
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
178
|
+
# company.errors.each_full{|msg| puts msg }
|
179
|
+
# # => Name is too short (minimum is 5 characters)
|
180
|
+
# # Name can't be blank
|
181
|
+
# # Address can't be blank
|
155
182
|
def each_full
|
156
183
|
full_messages.each { |msg| yield msg }
|
157
184
|
end
|
@@ -166,22 +193,24 @@ module ActiveRecord
|
|
166
193
|
# company = Company.create(:address => '123 First St.')
|
167
194
|
# company.errors.full_messages # =>
|
168
195
|
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
169
|
-
def full_messages
|
196
|
+
def full_messages(options = {})
|
170
197
|
full_messages = []
|
171
198
|
|
172
199
|
@errors.each_key do |attr|
|
173
|
-
@errors[attr].each do |
|
174
|
-
next
|
200
|
+
@errors[attr].each do |message|
|
201
|
+
next unless message
|
175
202
|
|
176
203
|
if attr == "base"
|
177
|
-
full_messages <<
|
204
|
+
full_messages << message
|
178
205
|
else
|
179
|
-
|
206
|
+
#key = :"activerecord.att.#{@base.class.name.underscore.to_sym}.#{attr}"
|
207
|
+
attr_name = @base.class.human_attribute_name(attr)
|
208
|
+
full_messages << attr_name + ' ' + message
|
180
209
|
end
|
181
210
|
end
|
182
211
|
end
|
183
212
|
full_messages
|
184
|
-
end
|
213
|
+
end
|
185
214
|
|
186
215
|
# Returns true if no errors have been added.
|
187
216
|
def empty?
|
@@ -209,13 +238,13 @@ module ActiveRecord
|
|
209
238
|
# end
|
210
239
|
#
|
211
240
|
# company = Company.create(:address => '123 First St.')
|
212
|
-
# company.errors.to_xml
|
213
|
-
#
|
214
|
-
# <errors>
|
215
|
-
# <error>Name is too short (minimum is 5 characters)</error>
|
216
|
-
# <error>Name can't be blank</error>
|
217
|
-
# <error>Address can't be blank</error>
|
218
|
-
# </errors>
|
241
|
+
# company.errors.to_xml
|
242
|
+
# # => <?xml version="1.0" encoding="UTF-8"?>
|
243
|
+
# # <errors>
|
244
|
+
# # <error>Name is too short (minimum is 5 characters)</error>
|
245
|
+
# # <error>Name can't be blank</error>
|
246
|
+
# # <error>Address can't be blank</error>
|
247
|
+
# # </errors>
|
219
248
|
def to_xml(options={})
|
220
249
|
options[:root] ||= "errors"
|
221
250
|
options[:indent] ||= 2
|
@@ -226,9 +255,12 @@ module ActiveRecord
|
|
226
255
|
full_messages.each { |msg| e.error(msg) }
|
227
256
|
end
|
228
257
|
end
|
258
|
+
|
229
259
|
end
|
230
260
|
|
231
261
|
|
262
|
+
# Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
|
263
|
+
#
|
232
264
|
# Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
|
233
265
|
# +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
|
234
266
|
# that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
|
@@ -261,14 +293,12 @@ module ActiveRecord
|
|
261
293
|
# person.errors.on "phone_number" # => "has invalid format"
|
262
294
|
# person.errors.each_full { |msg| puts msg }
|
263
295
|
# # => "Last name can't be empty\n" +
|
264
|
-
#
|
296
|
+
# # "Phone number has invalid format"
|
265
297
|
#
|
266
298
|
# person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
|
267
299
|
# person.save # => true (and person is now saved in the database)
|
268
300
|
#
|
269
301
|
# An Errors object is automatically created for every Active Record.
|
270
|
-
#
|
271
|
-
# Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
|
272
302
|
module Validations
|
273
303
|
VALIDATIONS = %w( validate validate_on_create validate_on_update )
|
274
304
|
|
@@ -277,16 +307,56 @@ module ActiveRecord
|
|
277
307
|
base.class_eval do
|
278
308
|
alias_method_chain :save, :validation
|
279
309
|
alias_method_chain :save!, :validation
|
280
|
-
alias_method_chain :update_attribute, :validation_skipping
|
281
310
|
end
|
282
311
|
|
283
312
|
base.send :include, ActiveSupport::Callbacks
|
284
313
|
base.define_callbacks *VALIDATIONS
|
285
314
|
end
|
286
315
|
|
287
|
-
#
|
288
|
-
#
|
289
|
-
#
|
316
|
+
# Active Record classes can implement validations in several ways. The highest level, easiest to read,
|
317
|
+
# and recommended approach is to use the declarative <tt>validates_..._of</tt> class methods (and
|
318
|
+
# +validates_associated+) documented below. These are sufficient for most model validations.
|
319
|
+
#
|
320
|
+
# Slightly lower level is +validates_each+. It provides some of the same options as the purely declarative
|
321
|
+
# validation methods, but like all the lower-level approaches it requires manually adding to the errors collection
|
322
|
+
# when the record is invalid.
|
323
|
+
#
|
324
|
+
# At a yet lower level, a model can use the class methods +validate+, +validate_on_create+ and +validate_on_update+
|
325
|
+
# to add validation methods or blocks. These are ActiveSupport::Callbacks and follow the same rules of inheritance
|
326
|
+
# and chaining.
|
327
|
+
#
|
328
|
+
# The lowest level style is to define the instance methods +validate+, +validate_on_create+ and +validate_on_update+
|
329
|
+
# as documented in ActiveRecord::Validations.
|
330
|
+
#
|
331
|
+
# == +validate+, +validate_on_create+ and +validate_on_update+ Class Methods
|
332
|
+
#
|
333
|
+
# Calls to these methods add a validation method or block to the class. Again, this approach is recommended
|
334
|
+
# only when the higher-level methods documented below (<tt>validates_..._of</tt> and +validates_associated+) are
|
335
|
+
# insufficient to handle the required validation.
|
336
|
+
#
|
337
|
+
# This can be done with a symbol pointing to a method:
|
338
|
+
#
|
339
|
+
# class Comment < ActiveRecord::Base
|
340
|
+
# validate :must_be_friends
|
341
|
+
#
|
342
|
+
# def must_be_friends
|
343
|
+
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
344
|
+
# end
|
345
|
+
# end
|
346
|
+
#
|
347
|
+
# Or with a block which is passed the current record to be validated:
|
348
|
+
#
|
349
|
+
# class Comment < ActiveRecord::Base
|
350
|
+
# validate do |comment|
|
351
|
+
# comment.must_be_friends
|
352
|
+
# end
|
353
|
+
#
|
354
|
+
# def must_be_friends
|
355
|
+
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
356
|
+
# end
|
357
|
+
# end
|
358
|
+
#
|
359
|
+
# This usage applies to +validate_on_create+ and +validate_on_update+ as well.
|
290
360
|
module ClassMethods
|
291
361
|
DEFAULT_VALIDATION_OPTIONS = {
|
292
362
|
:on => :save,
|
@@ -300,34 +370,6 @@ module ActiveRecord
|
|
300
370
|
:equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
|
301
371
|
:odd => 'odd?', :even => 'even?' }.freeze
|
302
372
|
|
303
|
-
# Adds a validation method or block to the class. This is useful when
|
304
|
-
# overriding the +validate+ instance method becomes too unwieldly and
|
305
|
-
# you're looking for more descriptive declaration of your validations.
|
306
|
-
#
|
307
|
-
# This can be done with a symbol pointing to a method:
|
308
|
-
#
|
309
|
-
# class Comment < ActiveRecord::Base
|
310
|
-
# validate :must_be_friends
|
311
|
-
#
|
312
|
-
# def must_be_friends
|
313
|
-
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
314
|
-
# end
|
315
|
-
# end
|
316
|
-
#
|
317
|
-
# Or with a block which is passed the current record to be validated:
|
318
|
-
#
|
319
|
-
# class Comment < ActiveRecord::Base
|
320
|
-
# validate do |comment|
|
321
|
-
# comment.must_be_friends
|
322
|
-
# end
|
323
|
-
#
|
324
|
-
# def must_be_friends
|
325
|
-
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
326
|
-
# end
|
327
|
-
# end
|
328
|
-
#
|
329
|
-
# This usage applies to +validate_on_create+ and +validate_on_update+ as well.
|
330
|
-
|
331
373
|
# Validates each attribute against a block.
|
332
374
|
#
|
333
375
|
# class Person < ActiveRecord::Base
|
@@ -389,13 +431,15 @@ module ActiveRecord
|
|
389
431
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
390
432
|
# method, proc or string should return or evaluate to a true or false value.
|
391
433
|
def validates_confirmation_of(*attr_names)
|
392
|
-
configuration = { :
|
434
|
+
configuration = { :on => :save }
|
393
435
|
configuration.update(attr_names.extract_options!)
|
394
436
|
|
395
437
|
attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
|
396
438
|
|
397
439
|
validates_each(attr_names, configuration) do |record, attr_name, value|
|
398
|
-
|
440
|
+
unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
|
441
|
+
record.errors.add(attr_name, :confirmation, :default => configuration[:message])
|
442
|
+
end
|
399
443
|
end
|
400
444
|
end
|
401
445
|
|
@@ -423,7 +467,7 @@ module ActiveRecord
|
|
423
467
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
424
468
|
# method, proc or string should return or evaluate to a true or false value.
|
425
469
|
def validates_acceptance_of(*attr_names)
|
426
|
-
configuration = { :
|
470
|
+
configuration = { :on => :save, :allow_nil => true, :accept => "1" }
|
427
471
|
configuration.update(attr_names.extract_options!)
|
428
472
|
|
429
473
|
db_cols = begin
|
@@ -435,7 +479,9 @@ module ActiveRecord
|
|
435
479
|
attr_accessor(*names)
|
436
480
|
|
437
481
|
validates_each(attr_names,configuration) do |record, attr_name, value|
|
438
|
-
|
482
|
+
unless value == configuration[:accept]
|
483
|
+
record.errors.add(attr_name, :accepted, :default => configuration[:message])
|
484
|
+
end
|
439
485
|
end
|
440
486
|
end
|
441
487
|
|
@@ -462,7 +508,7 @@ module ActiveRecord
|
|
462
508
|
# method, proc or string should return or evaluate to a true or false value.
|
463
509
|
#
|
464
510
|
def validates_presence_of(*attr_names)
|
465
|
-
configuration = { :
|
511
|
+
configuration = { :on => :save }
|
466
512
|
configuration.update(attr_names.extract_options!)
|
467
513
|
|
468
514
|
# can't use validates_each here, because it cannot cope with nonexistent attributes,
|
@@ -476,13 +522,13 @@ module ActiveRecord
|
|
476
522
|
#
|
477
523
|
# class Person < ActiveRecord::Base
|
478
524
|
# validates_length_of :first_name, :maximum=>30
|
479
|
-
# validates_length_of :last_name, :maximum=>30, :message=>"less than
|
525
|
+
# validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind"
|
480
526
|
# validates_length_of :fax, :in => 7..32, :allow_nil => true
|
481
527
|
# validates_length_of :phone, :in => 7..32, :allow_blank => true
|
482
528
|
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
|
483
|
-
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least
|
484
|
-
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with
|
485
|
-
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least
|
529
|
+
# validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character"
|
530
|
+
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me."
|
531
|
+
# validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
|
486
532
|
# end
|
487
533
|
#
|
488
534
|
# Configuration options:
|
@@ -493,9 +539,9 @@ module ActiveRecord
|
|
493
539
|
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
|
494
540
|
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
495
541
|
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
496
|
-
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is
|
497
|
-
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is
|
498
|
-
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be
|
542
|
+
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)").
|
543
|
+
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)").
|
544
|
+
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)").
|
499
545
|
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
|
500
546
|
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
501
547
|
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
@@ -510,10 +556,7 @@ module ActiveRecord
|
|
510
556
|
def validates_length_of(*attrs)
|
511
557
|
# Merge given options with defaults.
|
512
558
|
options = {
|
513
|
-
:
|
514
|
-
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
|
515
|
-
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length],
|
516
|
-
:tokenizer => lambda {|value| value.split(//)}
|
559
|
+
:tokenizer => lambda {|value| value.split(//)}
|
517
560
|
}.merge(DEFAULT_VALIDATION_OPTIONS)
|
518
561
|
options.update(attrs.extract_options!.symbolize_keys)
|
519
562
|
|
@@ -536,15 +579,12 @@ module ActiveRecord
|
|
536
579
|
when :within, :in
|
537
580
|
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
|
538
581
|
|
539
|
-
too_short = options[:too_short] % option_value.begin
|
540
|
-
too_long = options[:too_long] % option_value.end
|
541
|
-
|
542
582
|
validates_each(attrs, options) do |record, attr, value|
|
543
583
|
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
544
584
|
if value.nil? or value.size < option_value.begin
|
545
|
-
record.errors.add(attr, too_short)
|
585
|
+
record.errors.add(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
|
546
586
|
elsif value.size > option_value.end
|
547
|
-
record.errors.add(attr, too_long)
|
587
|
+
record.errors.add(attr, :too_long, :default => options[:too_long], :count => option_value.end)
|
548
588
|
end
|
549
589
|
end
|
550
590
|
when :is, :minimum, :maximum
|
@@ -554,11 +594,13 @@ module ActiveRecord
|
|
554
594
|
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
|
555
595
|
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
|
556
596
|
|
557
|
-
message = (options[:message] || options[message_options[option]]) % option_value
|
558
|
-
|
559
597
|
validates_each(attrs, options) do |record, attr, value|
|
560
598
|
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
561
|
-
|
599
|
+
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
|
600
|
+
key = message_options[option]
|
601
|
+
custom_message = options[:message] || options[key]
|
602
|
+
record.errors.add(attr, key, :default => custom_message, :count => option_value)
|
603
|
+
end
|
562
604
|
end
|
563
605
|
end
|
564
606
|
end
|
@@ -583,14 +625,10 @@ module ActiveRecord
|
|
583
625
|
# When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
|
584
626
|
# attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
|
585
627
|
#
|
586
|
-
# Because this check is performed outside the database there is still a chance that duplicate values
|
587
|
-
# will be inserted in two parallel transactions. To guarantee against this you should create a
|
588
|
-
# unique index on the field. See +add_index+ for more information.
|
589
|
-
#
|
590
628
|
# Configuration options:
|
591
629
|
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
|
592
630
|
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
|
593
|
-
# * <tt>:case_sensitive</tt> - Looks for an exact match.
|
631
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
|
594
632
|
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
|
595
633
|
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
|
596
634
|
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
@@ -599,8 +637,72 @@ module ActiveRecord
|
|
599
637
|
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
600
638
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
601
639
|
# method, proc or string should return or evaluate to a true or false value.
|
640
|
+
#
|
641
|
+
# === Concurrency and integrity
|
642
|
+
#
|
643
|
+
# Using this validation method in conjunction with ActiveRecord::Base#save
|
644
|
+
# does not guarantee the absence of duplicate record insertions, because
|
645
|
+
# uniqueness checks on the application level are inherently prone to race
|
646
|
+
# conditions. For example, suppose that two users try to post a Comment at
|
647
|
+
# the same time, and a Comment's title must be unique. At the database-level,
|
648
|
+
# the actions performed by these users could be interleaved in the following manner:
|
649
|
+
#
|
650
|
+
# User 1 | User 2
|
651
|
+
# ------------------------------------+--------------------------------------
|
652
|
+
# # User 1 checks whether there's |
|
653
|
+
# # already a comment with the title |
|
654
|
+
# # 'My Post'. This is not the case. |
|
655
|
+
# SELECT * FROM comments |
|
656
|
+
# WHERE title = 'My Post' |
|
657
|
+
# |
|
658
|
+
# | # User 2 does the same thing and also
|
659
|
+
# | # infers that his title is unique.
|
660
|
+
# | SELECT * FROM comments
|
661
|
+
# | WHERE title = 'My Post'
|
662
|
+
# |
|
663
|
+
# # User 1 inserts his comment. |
|
664
|
+
# INSERT INTO comments |
|
665
|
+
# (title, content) VALUES |
|
666
|
+
# ('My Post', 'hi!') |
|
667
|
+
# |
|
668
|
+
# | # User 2 does the same thing.
|
669
|
+
# | INSERT INTO comments
|
670
|
+
# | (title, content) VALUES
|
671
|
+
# | ('My Post', 'hello!')
|
672
|
+
# |
|
673
|
+
# | # ^^^^^^
|
674
|
+
# | # Boom! We now have a duplicate
|
675
|
+
# | # title!
|
676
|
+
#
|
677
|
+
# This could even happen if you use transactions with the 'serializable'
|
678
|
+
# isolation level. There are several ways to get around this problem:
|
679
|
+
# - By locking the database table before validating, and unlocking it after
|
680
|
+
# saving. However, table locking is very expensive, and thus not
|
681
|
+
# recommended.
|
682
|
+
# - By locking a lock file before validating, and unlocking it after saving.
|
683
|
+
# This does not work if you've scaled your Rails application across
|
684
|
+
# multiple web servers (because they cannot share lock files, or cannot
|
685
|
+
# do that efficiently), and thus not recommended.
|
686
|
+
# - Creating a unique index on the field, by using
|
687
|
+
# ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the
|
688
|
+
# rare case that a race condition occurs, the database will guarantee
|
689
|
+
# the field's uniqueness.
|
690
|
+
#
|
691
|
+
# When the database catches such a duplicate insertion,
|
692
|
+
# ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid
|
693
|
+
# exception. You can either choose to let this error propagate (which
|
694
|
+
# will result in the default Rails exception page being shown), or you
|
695
|
+
# can catch it and restart the transaction (e.g. by telling the user
|
696
|
+
# that the title already exists, and asking him to re-enter the title).
|
697
|
+
# This technique is also known as optimistic concurrency control:
|
698
|
+
# http://en.wikipedia.org/wiki/Optimistic_concurrency_control
|
699
|
+
#
|
700
|
+
# Active Record currently provides no way to distinguish unique
|
701
|
+
# index constraint errors from other types of database errors, so you
|
702
|
+
# will have to parse the (database-specific) exception message to detect
|
703
|
+
# such a case.
|
602
704
|
def validates_uniqueness_of(*attr_names)
|
603
|
-
configuration = { :
|
705
|
+
configuration = { :case_sensitive => true }
|
604
706
|
configuration.update(attr_names.extract_options!)
|
605
707
|
|
606
708
|
validates_each(attr_names,configuration) do |record, attr_name, value|
|
@@ -620,18 +722,23 @@ module ActiveRecord
|
|
620
722
|
|
621
723
|
is_text_column = finder_class.columns_hash[attr_name.to_s].text?
|
622
724
|
|
623
|
-
if
|
725
|
+
if value.nil?
|
726
|
+
comparison_operator = "IS ?"
|
727
|
+
elsif is_text_column
|
728
|
+
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
624
729
|
value = value.to_s
|
730
|
+
else
|
731
|
+
comparison_operator = "= ?"
|
625
732
|
end
|
626
733
|
|
734
|
+
sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
|
735
|
+
|
627
736
|
if value.nil? || (configuration[:case_sensitive] || !is_text_column)
|
628
|
-
condition_sql = "#{
|
737
|
+
condition_sql = "#{sql_attribute} #{comparison_operator}"
|
629
738
|
condition_params = [value]
|
630
739
|
else
|
631
|
-
|
632
|
-
|
633
|
-
condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
|
634
|
-
condition_params = [value.chars.downcase.to_s]
|
740
|
+
condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}"
|
741
|
+
condition_params = [value.mb_chars.downcase]
|
635
742
|
end
|
636
743
|
|
637
744
|
if scope = configuration[:scope]
|
@@ -647,26 +754,10 @@ module ActiveRecord
|
|
647
754
|
condition_params << record.send(:id)
|
648
755
|
end
|
649
756
|
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
:select => "#{connection.quote_column_name(attr_name)}",
|
654
|
-
:from => "#{finder_class.quoted_table_name}",
|
655
|
-
:conditions => [condition_sql, *condition_params]
|
656
|
-
)
|
657
|
-
)
|
658
|
-
end
|
659
|
-
|
660
|
-
unless results.length.zero?
|
661
|
-
found = true
|
662
|
-
|
663
|
-
# As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate
|
664
|
-
# column in ruby when case sensitive option
|
665
|
-
if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
|
666
|
-
found = results.any? { |a| a[attr_name.to_s] == value }
|
757
|
+
finder_class.with_exclusive_scope do
|
758
|
+
if finder_class.exists?([condition_sql, *condition_params])
|
759
|
+
record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
|
667
760
|
end
|
668
|
-
|
669
|
-
record.errors.add(attr_name, configuration[:message]) if found
|
670
761
|
end
|
671
762
|
end
|
672
763
|
end
|
@@ -696,13 +787,15 @@ module ActiveRecord
|
|
696
787
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
697
788
|
# method, proc or string should return or evaluate to a true or false value.
|
698
789
|
def validates_format_of(*attr_names)
|
699
|
-
configuration = { :
|
790
|
+
configuration = { :on => :save, :with => nil }
|
700
791
|
configuration.update(attr_names.extract_options!)
|
701
792
|
|
702
793
|
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
|
703
794
|
|
704
795
|
validates_each(attr_names, configuration) do |record, attr_name, value|
|
705
|
-
|
796
|
+
unless value.to_s =~ configuration[:with]
|
797
|
+
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
|
798
|
+
end
|
706
799
|
end
|
707
800
|
end
|
708
801
|
|
@@ -711,7 +804,7 @@ module ActiveRecord
|
|
711
804
|
# class Person < ActiveRecord::Base
|
712
805
|
# validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!"
|
713
806
|
# validates_inclusion_of :age, :in => 0..99
|
714
|
-
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension
|
807
|
+
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
|
715
808
|
# end
|
716
809
|
#
|
717
810
|
# Configuration options:
|
@@ -726,15 +819,17 @@ module ActiveRecord
|
|
726
819
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
727
820
|
# method, proc or string should return or evaluate to a true or false value.
|
728
821
|
def validates_inclusion_of(*attr_names)
|
729
|
-
configuration = { :
|
822
|
+
configuration = { :on => :save }
|
730
823
|
configuration.update(attr_names.extract_options!)
|
731
824
|
|
732
825
|
enum = configuration[:in] || configuration[:within]
|
733
826
|
|
734
|
-
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?(
|
827
|
+
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?)
|
735
828
|
|
736
829
|
validates_each(attr_names, configuration) do |record, attr_name, value|
|
737
|
-
|
830
|
+
unless enum.include?(value)
|
831
|
+
record.errors.add(attr_name, :inclusion, :default => configuration[:message], :value => value)
|
832
|
+
end
|
738
833
|
end
|
739
834
|
end
|
740
835
|
|
@@ -743,7 +838,7 @@ module ActiveRecord
|
|
743
838
|
# class Person < ActiveRecord::Base
|
744
839
|
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
|
745
840
|
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
|
746
|
-
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension
|
841
|
+
# validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed"
|
747
842
|
# end
|
748
843
|
#
|
749
844
|
# Configuration options:
|
@@ -758,15 +853,17 @@ module ActiveRecord
|
|
758
853
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
759
854
|
# method, proc or string should return or evaluate to a true or false value.
|
760
855
|
def validates_exclusion_of(*attr_names)
|
761
|
-
configuration = { :
|
856
|
+
configuration = { :on => :save }
|
762
857
|
configuration.update(attr_names.extract_options!)
|
763
858
|
|
764
859
|
enum = configuration[:in] || configuration[:within]
|
765
860
|
|
766
|
-
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?(
|
861
|
+
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?)
|
767
862
|
|
768
863
|
validates_each(attr_names, configuration) do |record, attr_name, value|
|
769
|
-
|
864
|
+
if enum.include?(value)
|
865
|
+
record.errors.add(attr_name, :exclusion, :default => configuration[:message], :value => value)
|
866
|
+
end
|
770
867
|
end
|
771
868
|
end
|
772
869
|
|
@@ -802,12 +899,13 @@ module ActiveRecord
|
|
802
899
|
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
803
900
|
# method, proc or string should return or evaluate to a true or false value.
|
804
901
|
def validates_associated(*attr_names)
|
805
|
-
configuration = { :
|
902
|
+
configuration = { :on => :save }
|
806
903
|
configuration.update(attr_names.extract_options!)
|
807
904
|
|
808
905
|
validates_each(attr_names, configuration) do |record, attr_name, value|
|
809
|
-
|
810
|
-
|
906
|
+
unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
|
907
|
+
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
|
908
|
+
end
|
811
909
|
end
|
812
910
|
end
|
813
911
|
|
@@ -855,15 +953,15 @@ module ActiveRecord
|
|
855
953
|
|
856
954
|
if configuration[:only_integer]
|
857
955
|
unless raw_value.to_s =~ /\A[+-]?\d+\Z/
|
858
|
-
record.errors.add(attr_name,
|
956
|
+
record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
|
859
957
|
next
|
860
958
|
end
|
861
959
|
raw_value = raw_value.to_i
|
862
960
|
else
|
863
|
-
|
961
|
+
begin
|
864
962
|
raw_value = Kernel.Float(raw_value)
|
865
963
|
rescue ArgumentError, TypeError
|
866
|
-
record.errors.add(attr_name,
|
964
|
+
record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
|
867
965
|
next
|
868
966
|
end
|
869
967
|
end
|
@@ -871,11 +969,11 @@ module ActiveRecord
|
|
871
969
|
numericality_options.each do |option|
|
872
970
|
case option
|
873
971
|
when :odd, :even
|
874
|
-
|
972
|
+
unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
|
973
|
+
record.errors.add(attr_name, option, :value => raw_value, :default => configuration[:message])
|
974
|
+
end
|
875
975
|
else
|
876
|
-
|
877
|
-
message = message % configuration[option] if configuration[option]
|
878
|
-
record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
|
976
|
+
record.errors.add(attr_name, option, :default => configuration[:message], :value => raw_value, :count => configuration[option]) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
|
879
977
|
end
|
880
978
|
end
|
881
979
|
end
|
@@ -924,14 +1022,6 @@ module ActiveRecord
|
|
924
1022
|
end
|
925
1023
|
end
|
926
1024
|
|
927
|
-
# Updates a single attribute and saves the record without going through the normal validation procedure.
|
928
|
-
# This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
|
929
|
-
# in Base is replaced with this when the validations module is mixed in, which it is by default.
|
930
|
-
def update_attribute_with_validation_skipping(name, value)
|
931
|
-
send(name.to_s + '=', value)
|
932
|
-
save(false)
|
933
|
-
end
|
934
|
-
|
935
1025
|
# Runs +validate+ and +validate_on_create+ or +validate_on_update+ and returns true if no errors were added otherwise false.
|
936
1026
|
def valid?
|
937
1027
|
errors.clear
|