activerecord 1.6.0 → 1.7.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.
- data/CHANGELOG +78 -0
- data/README +20 -29
- data/RUNNING_UNIT_TESTS +1 -2
- data/examples/validation.rb +0 -3
- data/install.rb +3 -16
- data/lib/active_record.rb +11 -4
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations.rb +8 -8
- data/lib/active_record/associations/association_collection.rb +1 -1
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -1
- data/lib/active_record/base.rb +117 -43
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract_adapter.rb +7 -14
- data/lib/active_record/connection_adapters/db2_adapter.rb +33 -22
- data/lib/active_record/connection_adapters/mysql_adapter.rb +74 -33
- data/lib/active_record/connection_adapters/oci_adapter.rb +265 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -3
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +13 -4
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +158 -67
- data/lib/active_record/deprecated_associations.rb +4 -4
- data/lib/active_record/fixtures.rb +12 -5
- data/lib/active_record/locking.rb +22 -22
- data/lib/active_record/observer.rb +6 -3
- data/lib/active_record/timestamp.rb +15 -5
- data/lib/active_record/transactions.rb +4 -4
- data/lib/active_record/validations.rb +272 -189
- data/lib/active_record/wrappings.rb +2 -2
- data/rakefile +17 -2
- data/test/aaa_create_tables_test.rb +58 -0
- data/test/abstract_unit.rb +3 -2
- data/test/aggregations_test.rb +0 -1
- data/test/associations_test.rb +27 -28
- data/test/base_test.rb +74 -2
- data/test/binary_test.rb +6 -2
- data/test/class_inheritable_attributes_test.rb +1 -1
- data/test/column_alias_test.rb +9 -2
- data/test/connections/native_oci/connection.rb +25 -0
- data/test/connections/native_sqlite/connection.rb +4 -1
- data/test/connections/native_sqlite3/connection.rb +4 -2
- data/test/deprecated_associations_test.rb +4 -5
- data/test/finder_test.rb +20 -4
- data/test/fixtures/db_definitions/create_oracle_db.bat +5 -0
- data/test/fixtures/db_definitions/create_oracle_db.sh +5 -0
- data/test/fixtures/db_definitions/db2.drop.sql +18 -0
- data/test/fixtures/db_definitions/db2.sql +1 -0
- data/test/fixtures/db_definitions/db22.drop.sql +2 -0
- data/test/fixtures/db_definitions/db22.sql +1 -0
- data/test/fixtures/db_definitions/drop_oracle_tables.sql +35 -0
- data/test/fixtures/db_definitions/drop_oracle_tables2.sql +3 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +18 -0
- data/test/fixtures/db_definitions/mysql.sql +2 -1
- data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/mysql2.sql +1 -0
- data/test/fixtures/db_definitions/oci.drop.sql +18 -0
- data/test/fixtures/db_definitions/oci.sql +167 -0
- data/test/fixtures/db_definitions/oci2.drop.sql +2 -0
- data/test/fixtures/db_definitions/oci2.sql +6 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +18 -0
- data/test/fixtures/db_definitions/postgresql.sql +2 -1
- data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
- data/test/fixtures/db_definitions/postgresql2.sql +2 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +18 -0
- data/test/fixtures/db_definitions/sqlite.sql +2 -1
- data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlite2.sql +1 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +18 -0
- data/test/fixtures/db_definitions/sqlserver.sql +1 -0
- data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
- data/test/fixtures/db_definitions/sqlserver2.sql +1 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/topics.yml +3 -3
- data/test/lifecycle_test.rb +0 -1
- data/test/modules_test.rb +0 -1
- data/test/reflection_test.rb +0 -1
- data/test/validations_test.rb +229 -41
- metadata +36 -28
- data/dev-utils/eval_debugger.rb +0 -14
- data/lib/active_record/support/binding_of_caller.rb +0 -83
- data/lib/active_record/support/breakpoint.rb +0 -518
- data/lib/active_record/support/class_attribute_accessors.rb +0 -57
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -117
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/core_ext.rb +0 -1
- data/lib/active_record/support/core_ext/hash.rb +0 -5
- data/lib/active_record/support/core_ext/hash/keys.rb +0 -35
- data/lib/active_record/support/core_ext/numeric.rb +0 -7
- data/lib/active_record/support/core_ext/numeric/bytes.rb +0 -33
- data/lib/active_record/support/core_ext/numeric/time.rb +0 -59
- data/lib/active_record/support/core_ext/object_and_class.rb +0 -24
- data/lib/active_record/support/core_ext/string.rb +0 -5
- data/lib/active_record/support/core_ext/string/inflections.rb +0 -45
- data/lib/active_record/support/dependencies.rb +0 -63
- data/lib/active_record/support/inflector.rb +0 -84
- data/lib/active_record/support/misc.rb +0 -8
- data/lib/active_record/support/module_attribute_accessors.rb +0 -57
@@ -1,25 +1,25 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
# Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
|
3
|
+
# record increments the lock_version column and the locking facilities ensure that records instantiated twice
|
4
|
+
# will let the last one saved raise a StaleObjectError if the first was also updated. Example:
|
5
|
+
#
|
6
|
+
# p1 = Person.find(1)
|
7
|
+
# p2 = Person.find(1)
|
8
|
+
#
|
9
|
+
# p1.first_name = "Michael"
|
10
|
+
# p1.save
|
11
|
+
#
|
12
|
+
# p2.first_name = "should fail"
|
13
|
+
# p2.save # Raises a ActiveRecord::StaleObjectError
|
14
|
+
#
|
15
|
+
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
16
|
+
# or otherwise apply the business logic needed to resolve the conflict.
|
17
|
+
#
|
18
|
+
# You must ensure that your database schema defaults the lock_version column to 0.
|
19
|
+
#
|
20
|
+
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
|
2
21
|
module Locking
|
3
|
-
|
4
|
-
# record increments the lock_version column and the locking facilities ensure that records instantiated twice
|
5
|
-
# will let the last one saved raise a StaleObjectError if the first was also updated. Example:
|
6
|
-
#
|
7
|
-
# p1 = Person.find(1)
|
8
|
-
# p2 = Person.find(1)
|
9
|
-
#
|
10
|
-
# p1.first_name = "Michael"
|
11
|
-
# p1.save
|
12
|
-
#
|
13
|
-
# p2.first_name = "should fail"
|
14
|
-
# p2.save # Raises a ActiveRecord::StaleObjectError
|
15
|
-
#
|
16
|
-
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
17
|
-
# or otherwise apply the business logic needed to resolve the conflict.
|
18
|
-
#
|
19
|
-
# You must ensure that your database schema defaults the lock_version column to 0.
|
20
|
-
#
|
21
|
-
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
|
22
|
-
def self.append_features(base)
|
22
|
+
def self.append_features(base) #:nodoc:
|
23
23
|
super
|
24
24
|
base.class_eval do
|
25
25
|
alias_method :update_without_lock, :update
|
@@ -27,7 +27,7 @@ module ActiveRecord
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def update_with_lock
|
30
|
+
def update_with_lock #:nodoc:
|
31
31
|
if locking_enabled?
|
32
32
|
previous_value = self.lock_version
|
33
33
|
self.lock_version = previous_value + 1
|
@@ -50,7 +50,7 @@ module ActiveRecord
|
|
50
50
|
@@lock_optimistically = true
|
51
51
|
cattr_accessor :lock_optimistically
|
52
52
|
|
53
|
-
def locking_enabled?
|
53
|
+
def locking_enabled? #:nodoc:
|
54
54
|
lock_optimistically && respond_to?(:lock_version)
|
55
55
|
end
|
56
56
|
end
|
@@ -21,7 +21,8 @@ module ActiveRecord
|
|
21
21
|
# something else than the class you're interested in observing, you can implement the observed_class class method. Like this:
|
22
22
|
#
|
23
23
|
# class AuditObserver < ActiveRecord::Observer
|
24
|
-
#
|
24
|
+
# observe Account
|
25
|
+
#
|
25
26
|
# def after_update(account)
|
26
27
|
# AuditTrail.new(account, "UPDATED")
|
27
28
|
# end
|
@@ -32,7 +33,8 @@ module ActiveRecord
|
|
32
33
|
# If the audit observer needs to watch more than one kind of object, this can be specified in an array, like this:
|
33
34
|
#
|
34
35
|
# class AuditObserver < ActiveRecord::Observer
|
35
|
-
#
|
36
|
+
# observe Account, Balance
|
37
|
+
#
|
36
38
|
# def after_update(record)
|
37
39
|
# AuditTrail.new(record, "UPDATED")
|
38
40
|
# end
|
@@ -44,6 +46,7 @@ module ActiveRecord
|
|
44
46
|
class Observer
|
45
47
|
include Singleton
|
46
48
|
|
49
|
+
# Attaches the observer to the supplied model classes.
|
47
50
|
def self.observe(*models)
|
48
51
|
define_method(:observed_class) { models }
|
49
52
|
end
|
@@ -55,7 +58,7 @@ module ActiveRecord
|
|
55
58
|
end
|
56
59
|
end
|
57
60
|
|
58
|
-
def update(callback_method, object)
|
61
|
+
def update(callback_method, object) #:nodoc:
|
59
62
|
send(callback_method, object) if respond_to?(callback_method)
|
60
63
|
end
|
61
64
|
|
@@ -18,8 +18,8 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
def create_with_timestamps
|
22
|
-
t =
|
21
|
+
def create_with_timestamps #:nodoc:
|
22
|
+
t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
|
23
23
|
write_attribute("created_at", t) if record_timestamps && respond_to?(:created_at) && created_at.nil?
|
24
24
|
write_attribute("created_on", t) if record_timestamps && respond_to?(:created_on) && created_on.nil?
|
25
25
|
|
@@ -29,8 +29,8 @@ module ActiveRecord
|
|
29
29
|
create_without_timestamps
|
30
30
|
end
|
31
31
|
|
32
|
-
def update_with_timestamps
|
33
|
-
t =
|
32
|
+
def update_with_timestamps #:nodoc:
|
33
|
+
t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
|
34
34
|
write_attribute("updated_at", t) if record_timestamps && respond_to?(:updated_at)
|
35
35
|
write_attribute("updated_on", t) if record_timestamps && respond_to?(:updated_on)
|
36
36
|
|
@@ -44,7 +44,17 @@ module ActiveRecord
|
|
44
44
|
# if the table has columns of either of these names. This feature is turned on by default.
|
45
45
|
@@record_timestamps = true
|
46
46
|
cattr_accessor :record_timestamps
|
47
|
+
|
48
|
+
# deprecated: use ActiveRecord::Base.default_timezone instead.
|
47
49
|
@@timestamps_gmt = false
|
48
|
-
|
50
|
+
def self.timestamps_gmt=( gmt ) #:nodoc:
|
51
|
+
warn "timestamps_gmt= is deprecated. use default_timezone= instead"
|
52
|
+
self.default_timezone = ( gmt ? :utc : :local )
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.timestamps_gmt #:nodoc:
|
56
|
+
warn "timestamps_gmt is deprecated. use default_timezone instead"
|
57
|
+
self.default_timezone == :utc
|
58
|
+
end
|
49
59
|
end
|
50
60
|
end
|
@@ -20,7 +20,7 @@ module ActiveRecord
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
|
23
|
-
# The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal
|
23
|
+
# The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
|
24
24
|
# vice versa. Transaction enforce the integrity of the database and guards the data against program errors or database break-downs.
|
25
25
|
# So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
|
26
26
|
# not at all. Example:
|
@@ -96,14 +96,14 @@ module ActiveRecord
|
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
|
-
def lock_mutex
|
99
|
+
def lock_mutex#:nodoc:
|
100
100
|
Thread.current['open_transactions'] ||= 0
|
101
101
|
TRANSACTION_MUTEX.lock if Thread.current['open_transactions'] == 0
|
102
102
|
Thread.current['start_db_transaction'] = (Thread.current['open_transactions'] == 0)
|
103
103
|
Thread.current['open_transactions'] += 1
|
104
104
|
end
|
105
105
|
|
106
|
-
def unlock_mutex
|
106
|
+
def unlock_mutex#:nodoc:
|
107
107
|
Thread.current['open_transactions'] -= 1
|
108
108
|
TRANSACTION_MUTEX.unlock if Thread.current['open_transactions'] == 0
|
109
109
|
end
|
@@ -121,4 +121,4 @@ module ActiveRecord
|
|
121
121
|
transaction { save_without_transactions(perform_validation) }
|
122
122
|
end
|
123
123
|
end
|
124
|
-
end
|
124
|
+
end
|
@@ -1,4 +1,140 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
# Active Record validation is reported to and from this object, which is used by Base#save to
|
3
|
+
# determine whether the object in a valid state to be saved. See usage example in Validations.
|
4
|
+
class Errors
|
5
|
+
def initialize(base) # :nodoc:
|
6
|
+
@base, @errors = base, {}
|
7
|
+
end
|
8
|
+
|
9
|
+
@@default_error_messages = {
|
10
|
+
:inclusion => "is not included in the list",
|
11
|
+
:invalid => "is invalid",
|
12
|
+
:confirmation => "doesn't match confirmation",
|
13
|
+
:accepted => "must be accepted",
|
14
|
+
:empty => "can't be empty",
|
15
|
+
:too_long => "is too long (max is %d characters)",
|
16
|
+
:too_short => "is too short (min is %d characters)",
|
17
|
+
:wrong_length => "is the wrong length (should be %d characters)",
|
18
|
+
:taken => "has already been taken",
|
19
|
+
}
|
20
|
+
|
21
|
+
# Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
|
22
|
+
cattr_accessor :default_error_messages
|
23
|
+
|
24
|
+
|
25
|
+
# Adds an error to the base object instead of any particular attribute. This is used
|
26
|
+
# to report errors that doesn't tie to any specific attribute, but rather to the object
|
27
|
+
# as a whole. These error messages doesn't get prepended with any field name when iterating
|
28
|
+
# with each_full, so they should be complete sentences.
|
29
|
+
def add_to_base(msg)
|
30
|
+
add(:base, msg)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
|
34
|
+
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
|
35
|
+
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
36
|
+
# If no +msg+ is supplied, "invalid" is assumed.
|
37
|
+
def add(attribute, msg = @@default_error_messages[:invalid])
|
38
|
+
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
|
39
|
+
@errors[attribute.to_s] << msg
|
40
|
+
end
|
41
|
+
|
42
|
+
# Will add an error message to each of the attributes in +attributes+ that is empty (defined by <tt>attribute_present?</tt>).
|
43
|
+
def add_on_empty(attributes, msg = @@default_error_messages[:empty])
|
44
|
+
for attr in [attributes].flatten
|
45
|
+
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
46
|
+
is_empty = value.respond_to?("empty?") ? value.empty? : false
|
47
|
+
add(attr, msg) unless !value.nil? && !is_empty
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
|
52
|
+
# If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
|
53
|
+
def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
|
54
|
+
for attr in [attributes].flatten
|
55
|
+
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
56
|
+
add(attr, too_short_msg % range.begin) if value && value.length < range.begin
|
57
|
+
add(attr, too_long_msg % range.end) if value && value.length > range.end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
alias :add_on_boundry_breaking :add_on_boundary_breaking
|
62
|
+
|
63
|
+
# Returns true if the specified +attribute+ has errors associated with it.
|
64
|
+
def invalid?(attribute)
|
65
|
+
!@errors[attribute.to_s].nil?
|
66
|
+
end
|
67
|
+
|
68
|
+
# * Returns nil, if no errors are associated with the specified +attribute+.
|
69
|
+
# * Returns the error message, if one error is associated with the specified +attribute+.
|
70
|
+
# * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
|
71
|
+
def on(attribute)
|
72
|
+
if @errors[attribute.to_s].nil?
|
73
|
+
nil
|
74
|
+
elsif @errors[attribute.to_s].length == 1
|
75
|
+
@errors[attribute.to_s].first
|
76
|
+
else
|
77
|
+
@errors[attribute.to_s]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
alias :[] :on
|
82
|
+
|
83
|
+
# Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
|
84
|
+
def on_base
|
85
|
+
on(:base)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Yields each attribute and associated message per error added.
|
89
|
+
def each
|
90
|
+
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
|
94
|
+
# through iteration as "First name can't be empty".
|
95
|
+
def each_full
|
96
|
+
full_messages.each { |msg| yield msg }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns all the full error messages in an array.
|
100
|
+
def full_messages
|
101
|
+
full_messages = []
|
102
|
+
|
103
|
+
@errors.each_key do |attr|
|
104
|
+
@errors[attr].each do |msg|
|
105
|
+
next if msg.nil?
|
106
|
+
|
107
|
+
if attr == "base"
|
108
|
+
full_messages << msg
|
109
|
+
else
|
110
|
+
full_messages << @base.class.human_attribute_name(attr) + " " + msg
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
return full_messages
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns true if no errors have been added.
|
119
|
+
def empty?
|
120
|
+
return @errors.empty?
|
121
|
+
end
|
122
|
+
|
123
|
+
# Removes all the errors that have been added.
|
124
|
+
def clear
|
125
|
+
@errors = {}
|
126
|
+
end
|
127
|
+
|
128
|
+
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such
|
129
|
+
# with this as well.
|
130
|
+
def count
|
131
|
+
error_count = 0
|
132
|
+
@errors.each_value { |attribute| error_count += attribute.length }
|
133
|
+
error_count
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
2
138
|
# Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
|
3
139
|
# +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
|
4
140
|
# that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
|
@@ -19,7 +155,7 @@ module ActiveRecord
|
|
19
155
|
# end
|
20
156
|
#
|
21
157
|
# def validate_on_update
|
22
|
-
# errors.add_to_base("No changes have
|
158
|
+
# errors.add_to_base("No changes have occurred") if unchanged_attributes?
|
23
159
|
# end
|
24
160
|
# end
|
25
161
|
#
|
@@ -40,27 +176,38 @@ module ActiveRecord
|
|
40
176
|
# Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
|
41
177
|
module Validations
|
42
178
|
VALIDATIONS = %w( validate validate_on_create validate_on_update )
|
43
|
-
|
179
|
+
|
44
180
|
def self.append_features(base) # :nodoc:
|
45
181
|
super
|
46
|
-
|
182
|
+
base.extend ClassMethods
|
47
183
|
base.class_eval do
|
48
184
|
alias_method :save_without_validation, :save
|
49
185
|
alias_method :save, :save_with_validation
|
50
186
|
|
51
187
|
alias_method :update_attribute_without_validation_skipping, :update_attribute
|
52
188
|
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") }
|
55
189
|
end
|
56
|
-
|
57
|
-
base.extend(ClassMethods)
|
58
190
|
end
|
59
191
|
|
60
192
|
# All of the following validations are defined in the class scope of the model that you're interested in validating.
|
61
193
|
# They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
|
62
194
|
# these over the low-level calls to validate and validate_on_create when possible.
|
63
195
|
module ClassMethods
|
196
|
+
def validate(*methods, &block)
|
197
|
+
methods << block if block_given?
|
198
|
+
write_inheritable_set(:validate, methods)
|
199
|
+
end
|
200
|
+
|
201
|
+
def validate_on_create(*methods, &block)
|
202
|
+
methods << block if block_given?
|
203
|
+
write_inheritable_set(:validate_on_create, methods)
|
204
|
+
end
|
205
|
+
|
206
|
+
def validate_on_update(*methods, &block)
|
207
|
+
methods << block if block_given?
|
208
|
+
write_inheritable_set(:validate_on_update, methods)
|
209
|
+
end
|
210
|
+
|
64
211
|
# Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
|
65
212
|
#
|
66
213
|
# Model:
|
@@ -83,6 +230,7 @@ module ActiveRecord
|
|
83
230
|
def validates_confirmation_of(*attr_names)
|
84
231
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
|
85
232
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
233
|
+
configuration[:message].gsub!(/\"/, '\\\\\"')
|
86
234
|
|
87
235
|
for attr_name in attr_names
|
88
236
|
attr_accessor "#{attr_name}_confirmation"
|
@@ -108,10 +256,11 @@ module ActiveRecord
|
|
108
256
|
def validates_acceptance_of(*attr_names)
|
109
257
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save }
|
110
258
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
259
|
+
configuration[:message].gsub!(/\"/, '\\\\\"')
|
111
260
|
|
112
261
|
for attr_name in attr_names
|
113
262
|
attr_accessor(attr_name)
|
114
|
-
class_eval(%(#{validation_method(configuration[:on])} %{errors.add('#{attr_name}',
|
263
|
+
class_eval(%(#{validation_method(configuration[:on])} %{errors.add('#{attr_name}', "#{configuration[:message]}") unless #{attr_name}.nil? or #{attr_name} == "1"}))
|
115
264
|
end
|
116
265
|
end
|
117
266
|
|
@@ -123,17 +272,61 @@ module ActiveRecord
|
|
123
272
|
def validates_presence_of(*attr_names)
|
124
273
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:empty], :on => :save }
|
125
274
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
275
|
+
configuration[:message].gsub!(/\"/, '\\\\\"')
|
126
276
|
|
127
277
|
for attr_name in attr_names
|
128
278
|
class_eval(%(#{validation_method(configuration[:on])} %{errors.add_on_empty('#{attr_name}', "#{configuration[:message]}")}))
|
129
279
|
end
|
130
280
|
end
|
131
|
-
|
281
|
+
|
282
|
+
|
283
|
+
DEFAULT_VALIDATION_OPTIONS = {
|
284
|
+
:on => :save,
|
285
|
+
:allow_nil => false,
|
286
|
+
:message => nil
|
287
|
+
}.freeze
|
288
|
+
|
289
|
+
DEFAULT_SIZE_VALIDATION_OPTIONS = DEFAULT_VALIDATION_OPTIONS.merge(
|
290
|
+
:too_long => ActiveRecord::Errors.default_error_messages[:too_long],
|
291
|
+
:too_short => ActiveRecord::Errors.default_error_messages[:too_short],
|
292
|
+
:wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
|
293
|
+
).freeze
|
294
|
+
|
295
|
+
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
|
296
|
+
|
297
|
+
|
298
|
+
# Validates each attribute against a block.
|
299
|
+
#
|
300
|
+
# class Person < ActiveRecord::Base
|
301
|
+
# validates_each :first_name, :last_name do |record, attr|
|
302
|
+
# record.errors.add attr, 'starts with z.' if attr[0] == ?z
|
303
|
+
# end
|
304
|
+
# end
|
305
|
+
#
|
306
|
+
# Options:
|
307
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
308
|
+
# * <tt>allow_nil</tt> - Skip validation if attribute is nil.
|
309
|
+
def validates_each(*attrs)
|
310
|
+
options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
|
311
|
+
attrs = attrs.flatten
|
312
|
+
|
313
|
+
# Declare the validation.
|
314
|
+
send(validation_method(options[:on] || :save)) do |record|
|
315
|
+
attrs.each do |attr|
|
316
|
+
value = record.send(attr)
|
317
|
+
next if value.nil? && options[:allow_nil]
|
318
|
+
yield record, attr, value
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
|
132
324
|
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
|
133
325
|
#
|
134
326
|
# class Person < ActiveRecord::Base
|
135
327
|
# validates_length_of :first_name, :maximum=>30
|
136
328
|
# validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
|
329
|
+
# validates_length_of :fax, :in => 7..32, :allow_nil => true
|
137
330
|
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
|
138
331
|
# validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
|
139
332
|
# validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
|
@@ -145,55 +338,68 @@ module ActiveRecord
|
|
145
338
|
# * <tt>is</tt> - The exact size of the attribute
|
146
339
|
# * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
|
147
340
|
# * <tt>in</tt> - A synonym(or alias) for :within
|
148
|
-
#
|
341
|
+
# * <tt>allow_nil</tt> - Attribute may be nil; skip validation.
|
342
|
+
#
|
149
343
|
# * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (max is %d characters)")
|
150
344
|
# * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
|
151
345
|
# * <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)")
|
152
346
|
# * <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
|
153
347
|
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
154
|
-
def validates_length_of(*
|
155
|
-
|
156
|
-
|
348
|
+
def validates_length_of(*attrs)
|
349
|
+
# Merge given options with defaults.
|
350
|
+
options = DEFAULT_SIZE_VALIDATION_OPTIONS.dup
|
351
|
+
options.update(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
|
352
|
+
|
353
|
+
# Ensure that one and only one range option is specified.
|
354
|
+
range_options = ALL_RANGE_OPTIONS & options.keys
|
355
|
+
case range_options.size
|
356
|
+
when 0
|
357
|
+
raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
|
358
|
+
when 1
|
359
|
+
# Valid number of options; do nothing.
|
360
|
+
else
|
361
|
+
raise ArgumentError, 'Too many range options specified. Choose only one.'
|
362
|
+
end
|
157
363
|
|
158
|
-
#
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
raise(ArgumentError, "The :within option must be a Range") unless within.kind_of?(Range)
|
177
|
-
class_eval(%(#{validation_method(configuration[:on])} %{errors.add_on_boundary_breaking('#{attr_name}', #{within}, "#{configuration[:too_long]}", "#{configuration[:too_short]}")}))
|
178
|
-
elsif maximum
|
179
|
-
raise(ArgumentError, "The :maximum option must be a Fixnum") unless maximum.kind_of?(Fixnum)
|
180
|
-
msg = configuration[:message] || configuration[:too_long]
|
181
|
-
msg = (msg % maximum) rescue msg
|
182
|
-
class_eval(%(#{validation_method(configuration[:on])} %{errors.add( '#{attr_name}', '#{msg}') if #{attr_name}.to_s.length > #{maximum} }))
|
183
|
-
elsif minimum
|
184
|
-
raise(ArgumentError, "The :minimum option must be a Fixnum") unless minimum.kind_of?(Fixnum)
|
185
|
-
msg = configuration[:message] || configuration[:too_short]
|
186
|
-
msg = (msg % minimum) rescue msg
|
187
|
-
class_eval(%(#{validation_method(configuration[:on])} %{errors.add( '#{attr_name}', '#{msg}') if #{attr_name}.to_s.length < #{minimum} }))
|
188
|
-
else
|
189
|
-
raise(ArgumentError, "The :is option must be a Fixnum") unless is.kind_of?(Fixnum)
|
190
|
-
msg = configuration[:message] || configuration[:wrong_length]
|
191
|
-
msg = (msg % is) rescue msg
|
192
|
-
class_eval(%(#{validation_method(configuration[:on])} %{errors.add( '#{attr_name}', '#{msg}') if #{attr_name}.to_s.length != #{is} }))
|
364
|
+
# Get range option and value.
|
365
|
+
option = range_options.first
|
366
|
+
option_value = options[range_options.first]
|
367
|
+
|
368
|
+
# Declare different validations per option.
|
369
|
+
case range_options.first
|
370
|
+
when :within, :in
|
371
|
+
raise ArgumentError, ':within must be a Range' unless option_value.is_a?(Range)
|
372
|
+
validates_each(attrs, options) do |record, attr|
|
373
|
+
next if record.send(attr).nil? and options[:allow_nil]
|
374
|
+
record.errors.add_on_boundary_breaking(attr, option_value, options[:too_long], options[:too_short])
|
375
|
+
end
|
376
|
+
when :is
|
377
|
+
raise ArgumentError, ':is must be a nonnegative Integer' unless option_value.is_a?(Integer) and option_value >= 0
|
378
|
+
message = options[:message] || options[:wrong_length]
|
379
|
+
message = (message % option_value) rescue message
|
380
|
+
validates_each(attrs, options) do |record, attr, value|
|
381
|
+
record.errors.add(attr, message) if value.nil? or value.size != option_value
|
193
382
|
end
|
194
|
-
|
383
|
+
when :minimum
|
384
|
+
raise ArgumentError, ':minimum must be a nonnegative Integer' unless option_value.is_a?(Integer) and option_value >= 0
|
385
|
+
message = options[:message] || options[:too_short]
|
386
|
+
message = (message % option_value) rescue message
|
387
|
+
validates_each(attrs, options) do |record, attr, value|
|
388
|
+
record.errors.add(attr, message) if value.nil? or value.size < option_value
|
389
|
+
end
|
390
|
+
when :maximum
|
391
|
+
raise ArgumentError, ':maximum must be a nonnegative Integer' unless option_value.is_a?(Integer) and option_value >= 0
|
392
|
+
message = options[:message] || options[:too_long]
|
393
|
+
message = (message % option_value) rescue message
|
394
|
+
validates_each(attrs, options) do |record, attr, value|
|
395
|
+
record.errors.add(attr, message) if value.nil? or value.size > option_value
|
396
|
+
end
|
397
|
+
end
|
195
398
|
end
|
196
399
|
|
400
|
+
alias_method :validates_size_of, :validates_length_of
|
401
|
+
|
402
|
+
|
197
403
|
# Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
|
198
404
|
# can be named "davidhh".
|
199
405
|
#
|
@@ -210,12 +416,13 @@ module ActiveRecord
|
|
210
416
|
def validates_uniqueness_of(*attr_names)
|
211
417
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
|
212
418
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
419
|
+
configuration[:message].gsub!(/\"/, '\\\\\"')
|
213
420
|
|
214
421
|
for attr_name in attr_names
|
215
422
|
if scope = configuration[:scope]
|
216
|
-
class_eval(%(validate %{errors.add('#{attr_name}',
|
423
|
+
class_eval(%(validate %{errors.add('#{attr_name}', "#{configuration[:message]}") if self.class.find_first(new_record? ? ['#{attr_name} = ? AND #{scope} = ?', #{attr_name}, #{scope}] : ["#{attr_name} = ? AND \\\#{self.class.primary_key} <> ? AND #{scope} = ?", #{attr_name}, id, #{scope}])}))
|
217
424
|
else
|
218
|
-
class_eval(%(validate %{errors.add('#{attr_name}',
|
425
|
+
class_eval(%(validate %{errors.add('#{attr_name}', "#{configuration[:message]}") if self.class.find_first(new_record? ? ['#{attr_name} = ?', #{attr_name}] : ["#{attr_name} = ? AND \\\#{self.class.primary_key} <> ?", #{attr_name}, id])}))
|
219
426
|
end
|
220
427
|
end
|
221
428
|
end
|
@@ -236,6 +443,7 @@ module ActiveRecord
|
|
236
443
|
def validates_format_of(*attr_names)
|
237
444
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
|
238
445
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
446
|
+
configuration[:message].gsub!(/\"/, '\\\\\"')
|
239
447
|
|
240
448
|
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
|
241
449
|
|
@@ -253,11 +461,13 @@ module ActiveRecord
|
|
253
461
|
#
|
254
462
|
# Configuration options:
|
255
463
|
# * <tt>in</tt> - An enumerable object of available items
|
256
|
-
# * <tt>message</tt> -
|
464
|
+
# * <tt>message</tt> - Specifies a customer error message (default is: "is not included in the list")
|
257
465
|
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
|
258
466
|
def validates_inclusion_of(*attr_names)
|
259
467
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
|
260
468
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
469
|
+
configuration[:message].gsub!(/\"/, '\\\\\"')
|
470
|
+
|
261
471
|
enum = configuration[:in] || configuration[:within]
|
262
472
|
allow_nil = configuration[:allow_nil]
|
263
473
|
|
@@ -272,7 +482,7 @@ module ActiveRecord
|
|
272
482
|
end
|
273
483
|
end
|
274
484
|
|
275
|
-
# Validates whether the associated object or objects are all themselves valid. Works with any kind of
|
485
|
+
# Validates whether the associated object or objects are all themselves valid. Works with any kind of association.
|
276
486
|
#
|
277
487
|
# class Book < ActiveRecord::Base
|
278
488
|
# has_many :pages
|
@@ -296,17 +506,23 @@ module ActiveRecord
|
|
296
506
|
def validates_associated(*attr_names)
|
297
507
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
|
298
508
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
509
|
+
configuration[:message].gsub!(/\"/, '\\\\\"')
|
299
510
|
|
300
511
|
for attr_name in attr_names
|
301
512
|
class_eval(%(#{validation_method(configuration[:on])} %{
|
302
513
|
errors.add("#{attr_name}", "#{configuration[:message]}") unless
|
303
|
-
(#{attr_name}.is_a?(Array) ? #{attr_name} : [#{attr_name}]).inject(true){ |memo, record|
|
514
|
+
(#{attr_name}.is_a?(Array) ? #{attr_name} : [#{attr_name}]).inject(true){ |memo, record| (record.nil? or record.valid?) and memo }
|
304
515
|
}))
|
305
516
|
end
|
306
517
|
end
|
307
518
|
|
308
519
|
|
309
520
|
private
|
521
|
+
def write_inheritable_set(key, methods)
|
522
|
+
existing_methods = read_inheritable_attribute(key) || []
|
523
|
+
write_inheritable_attribute(key, methods | existing_methods)
|
524
|
+
end
|
525
|
+
|
310
526
|
def validation_method(on)
|
311
527
|
case on
|
312
528
|
when :save then :validate
|
@@ -369,7 +585,7 @@ module ActiveRecord
|
|
369
585
|
|
370
586
|
private
|
371
587
|
def run_validations(validation_method)
|
372
|
-
validations = self.class.read_inheritable_attribute(validation_method.
|
588
|
+
validations = self.class.read_inheritable_attribute(validation_method.to_sym)
|
373
589
|
if validations.nil? then return end
|
374
590
|
validations.each do |validation|
|
375
591
|
if validation.is_a?(Symbol)
|
@@ -378,7 +594,7 @@ module ActiveRecord
|
|
378
594
|
eval(validation, binding)
|
379
595
|
elsif validation_block?(validation)
|
380
596
|
validation.call(self)
|
381
|
-
elsif
|
597
|
+
elsif validation_class?(validation, validation_method)
|
382
598
|
validation.send(validation_method, self)
|
383
599
|
else
|
384
600
|
raise(
|
@@ -398,137 +614,4 @@ module ActiveRecord
|
|
398
614
|
validation.respond_to?(validation_method)
|
399
615
|
end
|
400
616
|
end
|
401
|
-
|
402
|
-
# Active Record validation is reported to and from this object, which is used by Base#save to
|
403
|
-
# determine whether the object in a valid state to be saved. See usage example in Validations.
|
404
|
-
class Errors
|
405
|
-
def initialize(base) # :nodoc:
|
406
|
-
@base, @errors = base, {}
|
407
|
-
end
|
408
|
-
|
409
|
-
@@default_error_messages = {
|
410
|
-
:inclusion => "is not included in the list",
|
411
|
-
:invalid => "is invalid",
|
412
|
-
:confirmation => "doesn't match confirmation",
|
413
|
-
:accepted => "must be accepted",
|
414
|
-
:empty => "can't be empty",
|
415
|
-
:too_long => "is too long (max is %d characters)",
|
416
|
-
:too_short => "is too short (min is %d characters)",
|
417
|
-
:wrong_length => "is the wrong length (should be %d characters)",
|
418
|
-
:taken => "has already been taken",
|
419
|
-
}
|
420
|
-
cattr_accessor :default_error_messages
|
421
|
-
|
422
|
-
|
423
|
-
# Adds an error to the base object instead of any particular attribute. This is used
|
424
|
-
# to report errors that doesn't tie to any specific attribute, but rather to the object
|
425
|
-
# as a whole. These error messages doesn't get prepended with any field name when iterating
|
426
|
-
# with each_full, so they should be complete sentences.
|
427
|
-
def add_to_base(msg)
|
428
|
-
add(:base, msg)
|
429
|
-
end
|
430
|
-
|
431
|
-
# Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
|
432
|
-
# for the same attribute and ensure that this error object returns false when asked if +empty?+. More than one
|
433
|
-
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
434
|
-
# If no +msg+ is supplied, "invalid" is assumed.
|
435
|
-
def add(attribute, msg = @@default_error_messages[:invalid])
|
436
|
-
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
|
437
|
-
@errors[attribute.to_s] << msg
|
438
|
-
end
|
439
|
-
|
440
|
-
# Will add an error message to each of the attributes in +attributes+ that is empty (defined by <tt>attribute_present?</tt>).
|
441
|
-
def add_on_empty(attributes, msg = @@default_error_messages[:empty])
|
442
|
-
for attr in [attributes].flatten
|
443
|
-
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
444
|
-
is_empty = value.respond_to?("empty?") ? value.empty? : false
|
445
|
-
add(attr, msg) unless !value.nil? && !is_empty
|
446
|
-
end
|
447
|
-
end
|
448
|
-
|
449
|
-
# Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
|
450
|
-
# If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
|
451
|
-
def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
|
452
|
-
for attr in [attributes].flatten
|
453
|
-
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
454
|
-
add(attr, too_short_msg % range.begin) if value && value.length < range.begin
|
455
|
-
add(attr, too_long_msg % range.end) if value && value.length > range.end
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
|
-
alias :add_on_boundry_breaking :add_on_boundary_breaking
|
460
|
-
|
461
|
-
# Returns true if the specified +attribute+ has errors associated with it.
|
462
|
-
def invalid?(attribute)
|
463
|
-
!@errors[attribute.to_s].nil?
|
464
|
-
end
|
465
|
-
|
466
|
-
# * Returns nil, if no errors are associated with the specified +attribute+.
|
467
|
-
# * Returns the error message, if one error is associated with the specified +attribute+.
|
468
|
-
# * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
|
469
|
-
def on(attribute)
|
470
|
-
if @errors[attribute.to_s].nil?
|
471
|
-
nil
|
472
|
-
elsif @errors[attribute.to_s].length == 1
|
473
|
-
@errors[attribute.to_s].first
|
474
|
-
else
|
475
|
-
@errors[attribute.to_s]
|
476
|
-
end
|
477
|
-
end
|
478
|
-
|
479
|
-
alias :[] :on
|
480
|
-
|
481
|
-
# Returns errors assigned to base object through add_to_base according to the normal rules of on(attribute).
|
482
|
-
def on_base
|
483
|
-
on(:base)
|
484
|
-
end
|
485
|
-
|
486
|
-
# Yields each attribute and associated message per error added.
|
487
|
-
def each
|
488
|
-
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
489
|
-
end
|
490
|
-
|
491
|
-
# Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
|
492
|
-
# through iteration as "First name can't be empty".
|
493
|
-
def each_full
|
494
|
-
full_messages.each { |msg| yield msg }
|
495
|
-
end
|
496
|
-
|
497
|
-
# Returns all the full error messages in an array.
|
498
|
-
def full_messages
|
499
|
-
full_messages = []
|
500
|
-
|
501
|
-
@errors.each_key do |attr|
|
502
|
-
@errors[attr].each do |msg|
|
503
|
-
next if msg.nil?
|
504
|
-
|
505
|
-
if attr == "base"
|
506
|
-
full_messages << msg
|
507
|
-
else
|
508
|
-
full_messages << @base.class.human_attribute_name(attr) + " " + msg
|
509
|
-
end
|
510
|
-
end
|
511
|
-
end
|
512
|
-
|
513
|
-
return full_messages
|
514
|
-
end
|
515
|
-
|
516
|
-
# Returns true if no errors have been added.
|
517
|
-
def empty?
|
518
|
-
return @errors.empty?
|
519
|
-
end
|
520
|
-
|
521
|
-
# Removes all the errors that have been added.
|
522
|
-
def clear
|
523
|
-
@errors = {}
|
524
|
-
end
|
525
|
-
|
526
|
-
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such
|
527
|
-
# with this as well.
|
528
|
-
def count
|
529
|
-
error_count = 0
|
530
|
-
@errors.each_value { |attribute| error_count += attribute.length }
|
531
|
-
error_count
|
532
|
-
end
|
533
|
-
end
|
534
617
|
end
|