activerecord 5.2.2.1 → 5.2.5
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +116 -0
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +4 -5
- data/lib/active_record/associations/collection_proxy.rb +8 -34
- data/lib/active_record/associations/has_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_through_association.rb +6 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +28 -7
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/autosave_association.rb +20 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +33 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -8
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +6 -24
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -3
- data/lib/active_record/core.rb +2 -1
- data/lib/active_record/errors.rb +18 -12
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/migration/compatibility.rb +15 -15
- data/lib/active_record/persistence.rb +3 -1
- data/lib/active_record/querying.rb +1 -2
- data/lib/active_record/reflection.rb +10 -14
- data/lib/active_record/relation/calculations.rb +16 -12
- data/lib/active_record/relation/finder_methods.rb +6 -2
- data/lib/active_record/relation/merger.rb +6 -3
- data/lib/active_record/relation/predicate_builder.rb +14 -9
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +35 -10
- data/lib/active_record/scoping/default.rb +2 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/transactions.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 305cecf2df5a26287bc4f1a68e908bb0be484ab49d45d76a6e0ba1f2e99be96a
|
4
|
+
data.tar.gz: ec6f7cff6950599833fd0abca1d2133d632574d50c473596c2526f5bf16dd681
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ddcc02e64425cee7036e9faf08405e91b753f782b878bfb4ac16909ff2894cc37deff27bbf4500f933d262608dda6565c8a4364f195ecc9a8168bfa66aa7f28d
|
7
|
+
data.tar.gz: 9ec8898d502f1381d02e49ce42817a5b7a95d433a895b2fe53de8b3afd6b50af79f4cb1e863fbb634714fe80d663234a754e52dc63ed19caf0fc7b2ae4f6c5b5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,119 @@
|
|
1
|
+
## Rails 5.2.5 (March 26, 2021) ##
|
2
|
+
|
3
|
+
* No changes.
|
4
|
+
|
5
|
+
|
6
|
+
## Rails 5.2.4.5 (February 10, 2021) ##
|
7
|
+
|
8
|
+
* Fix possible DoS vector in PostgreSQL money type
|
9
|
+
|
10
|
+
Carefully crafted input can cause a DoS via the regular expressions used
|
11
|
+
for validating the money format in the PostgreSQL adapter. This patch
|
12
|
+
fixes the regexp.
|
13
|
+
|
14
|
+
Thanks to @dee-see from Hackerone for this patch!
|
15
|
+
|
16
|
+
[CVE-2021-22880]
|
17
|
+
|
18
|
+
*Aaron Patterson*
|
19
|
+
|
20
|
+
|
21
|
+
## Rails 5.2.4.4 (September 09, 2020) ##
|
22
|
+
|
23
|
+
* No changes.
|
24
|
+
|
25
|
+
|
26
|
+
## Rails 5.2.4.3 (May 18, 2020) ##
|
27
|
+
|
28
|
+
* No changes.
|
29
|
+
|
30
|
+
## Rails 5.2.4.2 (March 19, 2020) ##
|
31
|
+
|
32
|
+
* No changes.
|
33
|
+
|
34
|
+
|
35
|
+
## Rails 5.2.4.1 (December 18, 2019) ##
|
36
|
+
|
37
|
+
* No changes.
|
38
|
+
|
39
|
+
|
40
|
+
## Rails 5.2.4 (November 27, 2019) ##
|
41
|
+
|
42
|
+
* Fix circular `autosave: true` causes invalid records to be saved.
|
43
|
+
|
44
|
+
Prior to the fix, when there was a circular series of `autosave: true`
|
45
|
+
associations, the callback for a `has_many` association was run while
|
46
|
+
another instance of the same callback on the same association hadn't
|
47
|
+
finished running. When control returned to the first instance of the
|
48
|
+
callback, the instance variable had changed, and subsequent associated
|
49
|
+
records weren't saved correctly. Specifically, the ID field for the
|
50
|
+
`belongs_to` corresponding to the `has_many` was `nil`.
|
51
|
+
|
52
|
+
Fixes #28080.
|
53
|
+
|
54
|
+
*Larry Reid*
|
55
|
+
|
56
|
+
* PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute.
|
57
|
+
|
58
|
+
Fixes #36022.
|
59
|
+
|
60
|
+
*Ryuta Kamizono*
|
61
|
+
|
62
|
+
* Fix sqlite3 collation parsing when using decimal columns.
|
63
|
+
|
64
|
+
*Martin R. Schuster*
|
65
|
+
|
66
|
+
* Make ActiveRecord `ConnectionPool.connections` method thread-safe.
|
67
|
+
|
68
|
+
Fixes #36465.
|
69
|
+
|
70
|
+
*Jeff Doering*
|
71
|
+
|
72
|
+
* Assign all attributes before calling `build` to ensure the child record is visible in
|
73
|
+
`before_add` and `after_add` callbacks for `has_many :through` associations.
|
74
|
+
|
75
|
+
Fixes #33249.
|
76
|
+
|
77
|
+
*Ryan H. Kerr*
|
78
|
+
|
79
|
+
|
80
|
+
## Rails 5.2.3 (March 27, 2019) ##
|
81
|
+
|
82
|
+
* Fix different `count` calculation when using `size` with manual `select` with DISTINCT.
|
83
|
+
|
84
|
+
Fixes #35214.
|
85
|
+
|
86
|
+
*Juani Villarejo*
|
87
|
+
|
88
|
+
* Fix prepared statements caching to be enabled even when query caching is enabled.
|
89
|
+
|
90
|
+
*Ryuta Kamizono*
|
91
|
+
|
92
|
+
* Don't allow `where` with invalid value matches to nil values.
|
93
|
+
|
94
|
+
Fixes #33624.
|
95
|
+
|
96
|
+
*Ryuta Kamizono*
|
97
|
+
|
98
|
+
* Restore an ability that class level `update` without giving ids.
|
99
|
+
|
100
|
+
Fixes #34743.
|
101
|
+
|
102
|
+
*Ryuta Kamizono*
|
103
|
+
|
104
|
+
* Fix join table column quoting with SQLite.
|
105
|
+
|
106
|
+
*Gannon McGibbon*
|
107
|
+
|
108
|
+
* Ensure that `delete_all` on collection proxy returns affected count.
|
109
|
+
|
110
|
+
*Ryuta Kamizono*
|
111
|
+
|
112
|
+
* Reset scope after delete on collection association to clear stale offsets of removed records.
|
113
|
+
|
114
|
+
*Gannon McGibbon*
|
115
|
+
|
116
|
+
|
1
117
|
## Rails 5.2.2.1 (March 11, 2019) ##
|
2
118
|
|
3
119
|
* No changes.
|
@@ -20,10 +20,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
|
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
-
def self.define_extensions(model, name)
|
23
|
+
def self.define_extensions(model, name, &block)
|
24
24
|
if block_given?
|
25
25
|
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
|
26
|
-
extension = Module.new(&
|
26
|
+
extension = Module.new(&block)
|
27
27
|
model.parent.const_set(extension_module_name, extension)
|
28
28
|
end
|
29
29
|
end
|
@@ -109,9 +109,8 @@ module ActiveRecord
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
-
# Add +records+ to this association.
|
113
|
-
#
|
114
|
-
# +push+ and +concat+ behave identically.
|
112
|
+
# Add +records+ to this association. Since +<<+ flattens its argument list
|
113
|
+
# and inserts each record, +push+ and +concat+ behave identically.
|
115
114
|
def concat(*records)
|
116
115
|
records = records.flatten
|
117
116
|
if owner.new_record?
|
@@ -362,7 +361,6 @@ module ActiveRecord
|
|
362
361
|
add_to_target(record) do
|
363
362
|
result = insert_record(record, true, raise) {
|
364
363
|
@_was_loaded = loaded?
|
365
|
-
@association_ids = nil
|
366
364
|
}
|
367
365
|
end
|
368
366
|
raise ActiveRecord::Rollback unless result
|
@@ -399,6 +397,7 @@ module ActiveRecord
|
|
399
397
|
|
400
398
|
delete_records(existing_records, method) if existing_records.any?
|
401
399
|
records.each { |record| target.delete(record) }
|
400
|
+
@association_ids = nil
|
402
401
|
|
403
402
|
records.each { |record| callback(:after_remove, record) }
|
404
403
|
end
|
@@ -439,7 +438,6 @@ module ActiveRecord
|
|
439
438
|
unless owner.new_record?
|
440
439
|
result &&= insert_record(record, true, raise) {
|
441
440
|
@_was_loaded = loaded?
|
442
|
-
@association_ids = nil
|
443
441
|
}
|
444
442
|
end
|
445
443
|
end
|
@@ -462,6 +460,7 @@ module ActiveRecord
|
|
462
460
|
if index
|
463
461
|
target[index] = record
|
464
462
|
elsif @_was_loaded || !loaded?
|
463
|
+
@association_ids = nil
|
465
464
|
target << record
|
466
465
|
end
|
467
466
|
|
@@ -366,34 +366,6 @@ module ActiveRecord
|
|
366
366
|
@association.create!(attributes, &block)
|
367
367
|
end
|
368
368
|
|
369
|
-
# Add one or more records to the collection by setting their foreign keys
|
370
|
-
# to the association's primary key. Since #<< flattens its argument list and
|
371
|
-
# inserts each record, +push+ and #concat behave identically. Returns +self+
|
372
|
-
# so method calls may be chained.
|
373
|
-
#
|
374
|
-
# class Person < ActiveRecord::Base
|
375
|
-
# has_many :pets
|
376
|
-
# end
|
377
|
-
#
|
378
|
-
# person.pets.size # => 0
|
379
|
-
# person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
|
380
|
-
# person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
|
381
|
-
# person.pets.size # => 3
|
382
|
-
#
|
383
|
-
# person.id # => 1
|
384
|
-
# person.pets
|
385
|
-
# # => [
|
386
|
-
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
387
|
-
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
388
|
-
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
389
|
-
# # ]
|
390
|
-
#
|
391
|
-
# person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
|
392
|
-
# person.pets.size # => 5
|
393
|
-
def concat(*records)
|
394
|
-
@association.concat(*records)
|
395
|
-
end
|
396
|
-
|
397
369
|
# Replaces this collection with +other_array+. This will perform a diff
|
398
370
|
# and delete/add only records that have changed.
|
399
371
|
#
|
@@ -500,7 +472,7 @@ module ActiveRecord
|
|
500
472
|
# Pet.find(1, 2, 3)
|
501
473
|
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
|
502
474
|
def delete_all(dependent = nil)
|
503
|
-
@association.delete_all(dependent)
|
475
|
+
@association.delete_all(dependent).tap { reset_scope }
|
504
476
|
end
|
505
477
|
|
506
478
|
# Deletes the records of the collection directly from the database
|
@@ -527,7 +499,7 @@ module ActiveRecord
|
|
527
499
|
#
|
528
500
|
# Pet.find(1) # => Couldn't find Pet with id=1
|
529
501
|
def destroy_all
|
530
|
-
@association.destroy_all
|
502
|
+
@association.destroy_all.tap { reset_scope }
|
531
503
|
end
|
532
504
|
|
533
505
|
# Deletes the +records+ supplied from the collection according to the strategy
|
@@ -646,7 +618,7 @@ module ActiveRecord
|
|
646
618
|
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
647
619
|
# # ]
|
648
620
|
def delete(*records)
|
649
|
-
@association.delete(*records)
|
621
|
+
@association.delete(*records).tap { reset_scope }
|
650
622
|
end
|
651
623
|
|
652
624
|
# Destroys the +records+ supplied and removes them from the collection.
|
@@ -718,7 +690,7 @@ module ActiveRecord
|
|
718
690
|
#
|
719
691
|
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
|
720
692
|
def destroy(*records)
|
721
|
-
@association.destroy(*records)
|
693
|
+
@association.destroy(*records).tap { reset_scope }
|
722
694
|
end
|
723
695
|
|
724
696
|
##
|
@@ -1033,8 +1005,9 @@ module ActiveRecord
|
|
1033
1005
|
end
|
1034
1006
|
|
1035
1007
|
# Adds one or more +records+ to the collection by setting their foreign keys
|
1036
|
-
# to the association's primary key.
|
1037
|
-
#
|
1008
|
+
# to the association's primary key. Since +<<+ flattens its argument list and
|
1009
|
+
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
|
1010
|
+
# so several appends may be chained together.
|
1038
1011
|
#
|
1039
1012
|
# class Person < ActiveRecord::Base
|
1040
1013
|
# has_many :pets
|
@@ -1057,6 +1030,7 @@ module ActiveRecord
|
|
1057
1030
|
end
|
1058
1031
|
alias_method :push, :<<
|
1059
1032
|
alias_method :append, :<<
|
1033
|
+
alias_method :concat, :<<
|
1060
1034
|
|
1061
1035
|
def prepend(*args)
|
1062
1036
|
raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
|
@@ -57,21 +57,14 @@ module ActiveRecord
|
|
57
57
|
@through_records[record.object_id] ||= begin
|
58
58
|
ensure_mutable
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
attributes = through_scope_attributes
|
61
|
+
attributes[source_reflection.name] = record
|
62
|
+
attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
|
62
63
|
|
63
|
-
|
64
|
-
through_record.send("#{source_reflection.foreign_type}=", options[:source_type])
|
65
|
-
end
|
66
|
-
|
67
|
-
through_record
|
64
|
+
through_association.build(attributes)
|
68
65
|
end
|
69
66
|
end
|
70
67
|
|
71
|
-
def options_for_through_record
|
72
|
-
[through_scope_attributes]
|
73
|
-
end
|
74
|
-
|
75
68
|
def through_scope_attributes
|
76
69
|
scope.where_values_hash(through_association.reflection.name.to_s).
|
77
70
|
except!(through_association.reflection.foreign_key,
|
@@ -161,6 +154,8 @@ module ActiveRecord
|
|
161
154
|
else
|
162
155
|
update_counter(-count)
|
163
156
|
end
|
157
|
+
|
158
|
+
count
|
164
159
|
end
|
165
160
|
|
166
161
|
def difference(a, b)
|
@@ -30,17 +30,21 @@ module ActiveRecord
|
|
30
30
|
table = tables[-i]
|
31
31
|
klass = reflection.klass
|
32
32
|
|
33
|
-
|
33
|
+
join_scope = reflection.join_scope(table, foreign_table, foreign_klass)
|
34
34
|
|
35
|
-
joins << table.create_join(table, table.create_on(constraint), join_type)
|
36
|
-
|
37
|
-
join_scope = reflection.join_scope(table, foreign_klass)
|
38
35
|
arel = join_scope.arel(alias_tracker.aliases)
|
36
|
+
nodes = arel.constraints.first
|
37
|
+
|
38
|
+
others, children = nodes.children.partition do |node|
|
39
|
+
!fetch_arel_attribute(node) { |attr| attr.relation.name == table.name }
|
40
|
+
end
|
41
|
+
nodes = table.create_and(children)
|
39
42
|
|
40
|
-
|
43
|
+
joins << table.create_join(table, table.create_on(nodes), join_type)
|
44
|
+
|
45
|
+
unless others.empty?
|
41
46
|
joins.concat arel.join_sources
|
42
|
-
|
43
|
-
right.expr = right.expr.and(arel.constraints)
|
47
|
+
append_constraints(joins.last, others)
|
44
48
|
end
|
45
49
|
|
46
50
|
# The current table in this iteration becomes the foreign table in the next
|
@@ -54,6 +58,23 @@ module ActiveRecord
|
|
54
58
|
@tables = tables
|
55
59
|
@table = tables.first
|
56
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def fetch_arel_attribute(value)
|
64
|
+
case value
|
65
|
+
when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
|
66
|
+
yield value.left.is_a?(Arel::Attributes::Attribute) ? value.left : value.right
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def append_constraints(join, constraints)
|
71
|
+
if join.is_a?(Arel::Nodes::StringJoin)
|
72
|
+
join_string = table.create_and(constraints.unshift(join.left))
|
73
|
+
join.left = Arel.sql(base_klass.connection.visitor.compile(join_string))
|
74
|
+
else
|
75
|
+
join.right.expr.children.concat(constraints)
|
76
|
+
end
|
77
|
+
end
|
57
78
|
end
|
58
79
|
end
|
59
80
|
end
|
@@ -177,7 +177,7 @@ module ActiveRecord
|
|
177
177
|
# and attach it to a relation. The class returned implements a `run` method
|
178
178
|
# that accepts a preloader.
|
179
179
|
def preloader_for(reflection, owners)
|
180
|
-
if owners.
|
180
|
+
if owners.all? { |o| o.association(reflection.name).loaded? }
|
181
181
|
return AlreadyLoaded
|
182
182
|
end
|
183
183
|
reflection.check_preloadable!
|
@@ -272,7 +272,7 @@ module ActiveRecord
|
|
272
272
|
# or saved. If +autosave+ is +false+ only new records will be returned,
|
273
273
|
# unless the parent is/was a new record itself.
|
274
274
|
def associated_records_to_validate_or_save(association, new_record, autosave)
|
275
|
-
if new_record
|
275
|
+
if new_record || custom_validation_context?
|
276
276
|
association && association.target
|
277
277
|
elsif autosave
|
278
278
|
association.target.find_all(&:changed_for_autosave?)
|
@@ -304,7 +304,7 @@ module ActiveRecord
|
|
304
304
|
def validate_single_association(reflection)
|
305
305
|
association = association_instance_get(reflection.name)
|
306
306
|
record = association && association.reader
|
307
|
-
association_valid?(reflection, record) if record
|
307
|
+
association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
|
308
308
|
end
|
309
309
|
|
310
310
|
# Validate the associated records if <tt>:validate</tt> or
|
@@ -324,7 +324,7 @@ module ActiveRecord
|
|
324
324
|
def association_valid?(reflection, record, index = nil)
|
325
325
|
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
|
326
326
|
|
327
|
-
context = validation_context
|
327
|
+
context = validation_context if custom_validation_context?
|
328
328
|
|
329
329
|
unless valid = record.valid?(context)
|
330
330
|
if reflection.options[:autosave]
|
@@ -382,10 +382,14 @@ module ActiveRecord
|
|
382
382
|
if association = association_instance_get(reflection.name)
|
383
383
|
autosave = reflection.options[:autosave]
|
384
384
|
|
385
|
+
# By saving the instance variable in a local variable,
|
386
|
+
# we make the whole callback re-entrant.
|
387
|
+
new_record_before_save = @new_record_before_save
|
388
|
+
|
385
389
|
# reconstruct the scope now that we know the owner's id
|
386
390
|
association.reset_scope
|
387
391
|
|
388
|
-
if records = associated_records_to_validate_or_save(association,
|
392
|
+
if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
|
389
393
|
if autosave
|
390
394
|
records_to_destroy = records.select(&:marked_for_destruction?)
|
391
395
|
records_to_destroy.each { |record| association.destroy(record) }
|
@@ -397,7 +401,7 @@ module ActiveRecord
|
|
397
401
|
|
398
402
|
saved = true
|
399
403
|
|
400
|
-
if autosave != false && (
|
404
|
+
if autosave != false && (new_record_before_save || record.new_record?)
|
401
405
|
if autosave
|
402
406
|
saved = association.insert_record(record, false)
|
403
407
|
elsif !reflection.nested?
|
@@ -457,10 +461,16 @@ module ActiveRecord
|
|
457
461
|
# If the record is new or it has changed, returns true.
|
458
462
|
def record_changed?(reflection, record, key)
|
459
463
|
record.new_record? ||
|
460
|
-
|
464
|
+
association_foreign_key_changed?(reflection, record, key) ||
|
461
465
|
record.will_save_change_to_attribute?(reflection.foreign_key)
|
462
466
|
end
|
463
467
|
|
468
|
+
def association_foreign_key_changed?(reflection, record, key)
|
469
|
+
return false if reflection.through_reflection?
|
470
|
+
|
471
|
+
record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
|
472
|
+
end
|
473
|
+
|
464
474
|
# Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
|
465
475
|
#
|
466
476
|
# In addition, it will destroy the association if it was marked for destruction.
|
@@ -489,6 +499,10 @@ module ActiveRecord
|
|
489
499
|
end
|
490
500
|
end
|
491
501
|
|
502
|
+
def custom_validation_context?
|
503
|
+
validation_context && [:create, :update].exclude?(validation_context)
|
504
|
+
end
|
505
|
+
|
492
506
|
def _ensure_no_duplicate_errors
|
493
507
|
errors.messages.each_key do |attribute|
|
494
508
|
errors[attribute].uniq!
|