activerecord 5.2.2 → 5.2.4.4
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 +97 -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/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 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b371eab07ad19fdba3131ea0100df91b8b6b86cc95456d9a3499a6b872a2cd47
|
4
|
+
data.tar.gz: 83e774333487bd4f05ca4b33bcba5d3856a76deb08a1b9e8a2ab15a6dada07b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f90b220a5a56487c855b7ce357103e81933cbf8526aa5d166c89eb245d310a8e878104e553d6026881471c1d1fd79aad212651dc3a2f3a2b9d538c848156d175
|
7
|
+
data.tar.gz: def00b3f9d09476467c13b4df9ee720264f542b828a520f220688a3d1368d948cf2feba8a06ea4df87de0fbc617104e1928ab1ffa9e1c022cd86f97818feb031
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,100 @@
|
|
1
|
+
## Rails 5.2.4.4 (September 09, 2020) ##
|
2
|
+
|
3
|
+
* No changes.
|
4
|
+
|
5
|
+
|
6
|
+
## Rails 5.2.4.3 (May 18, 2020) ##
|
7
|
+
|
8
|
+
* No changes.
|
9
|
+
|
10
|
+
|
11
|
+
## Rails 5.2.4.1 (December 18, 2019) ##
|
12
|
+
|
13
|
+
* No changes.
|
14
|
+
|
15
|
+
|
16
|
+
## Rails 5.2.4 (November 27, 2019) ##
|
17
|
+
|
18
|
+
* Fix circular `autosave: true` causes invalid records to be saved.
|
19
|
+
|
20
|
+
Prior to the fix, when there was a circular series of `autosave: true`
|
21
|
+
associations, the callback for a `has_many` association was run while
|
22
|
+
another instance of the same callback on the same association hadn't
|
23
|
+
finished running. When control returned to the first instance of the
|
24
|
+
callback, the instance variable had changed, and subsequent associated
|
25
|
+
records weren't saved correctly. Specifically, the ID field for the
|
26
|
+
`belongs_to` corresponding to the `has_many` was `nil`.
|
27
|
+
|
28
|
+
Fixes #28080.
|
29
|
+
|
30
|
+
*Larry Reid*
|
31
|
+
|
32
|
+
* PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute.
|
33
|
+
|
34
|
+
Fixes #36022.
|
35
|
+
|
36
|
+
*Ryuta Kamizono*
|
37
|
+
|
38
|
+
* Fix sqlite3 collation parsing when using decimal columns.
|
39
|
+
|
40
|
+
*Martin R. Schuster*
|
41
|
+
|
42
|
+
* Make ActiveRecord `ConnectionPool.connections` method thread-safe.
|
43
|
+
|
44
|
+
Fixes #36465.
|
45
|
+
|
46
|
+
*Jeff Doering*
|
47
|
+
|
48
|
+
* Assign all attributes before calling `build` to ensure the child record is visible in
|
49
|
+
`before_add` and `after_add` callbacks for `has_many :through` associations.
|
50
|
+
|
51
|
+
Fixes #33249.
|
52
|
+
|
53
|
+
*Ryan H. Kerr*
|
54
|
+
|
55
|
+
|
56
|
+
## Rails 5.2.3 (March 27, 2019) ##
|
57
|
+
|
58
|
+
* Fix different `count` calculation when using `size` with manual `select` with DISTINCT.
|
59
|
+
|
60
|
+
Fixes #35214.
|
61
|
+
|
62
|
+
*Juani Villarejo*
|
63
|
+
|
64
|
+
* Fix prepared statements caching to be enabled even when query caching is enabled.
|
65
|
+
|
66
|
+
*Ryuta Kamizono*
|
67
|
+
|
68
|
+
* Don't allow `where` with invalid value matches to nil values.
|
69
|
+
|
70
|
+
Fixes #33624.
|
71
|
+
|
72
|
+
*Ryuta Kamizono*
|
73
|
+
|
74
|
+
* Restore an ability that class level `update` without giving ids.
|
75
|
+
|
76
|
+
Fixes #34743.
|
77
|
+
|
78
|
+
*Ryuta Kamizono*
|
79
|
+
|
80
|
+
* Fix join table column quoting with SQLite.
|
81
|
+
|
82
|
+
*Gannon McGibbon*
|
83
|
+
|
84
|
+
* Ensure that `delete_all` on collection proxy returns affected count.
|
85
|
+
|
86
|
+
*Ryuta Kamizono*
|
87
|
+
|
88
|
+
* Reset scope after delete on collection association to clear stale offsets of removed records.
|
89
|
+
|
90
|
+
*Gannon McGibbon*
|
91
|
+
|
92
|
+
|
93
|
+
## Rails 5.2.2.1 (March 11, 2019) ##
|
94
|
+
|
95
|
+
* No changes.
|
96
|
+
|
97
|
+
|
1
98
|
## Rails 5.2.2 (December 04, 2018) ##
|
2
99
|
|
3
100
|
* Do not ignore the scoping with query methods in the scope block.
|
@@ -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!
|
@@ -310,7 +310,7 @@ module ActiveRecord
|
|
310
310
|
include QueryCache::ConnectionPoolConfiguration
|
311
311
|
|
312
312
|
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
313
|
-
attr_reader :spec, :
|
313
|
+
attr_reader :spec, :size, :reaper
|
314
314
|
|
315
315
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
316
316
|
# object which describes database connection information (e.g. adapter,
|
@@ -379,7 +379,7 @@ module ActiveRecord
|
|
379
379
|
# #connection can be called any number of times; the connection is
|
380
380
|
# held in a cache keyed by a thread.
|
381
381
|
def connection
|
382
|
-
@thread_cached_conns[connection_cache_key(
|
382
|
+
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
|
383
383
|
end
|
384
384
|
|
385
385
|
# Returns true if there is an open connection being used for the current thread.
|
@@ -388,7 +388,7 @@ module ActiveRecord
|
|
388
388
|
# #connection or #with_connection methods. Connections obtained through
|
389
389
|
# #checkout will not be detected by #active_connection?
|
390
390
|
def active_connection?
|
391
|
-
@thread_cached_conns[connection_cache_key(
|
391
|
+
@thread_cached_conns[connection_cache_key(current_thread)]
|
392
392
|
end
|
393
393
|
|
394
394
|
# Signal that the thread is finished with the current connection.
|
@@ -423,6 +423,21 @@ module ActiveRecord
|
|
423
423
|
synchronize { @connections.any? }
|
424
424
|
end
|
425
425
|
|
426
|
+
# Returns an array containing the connections currently in the pool.
|
427
|
+
# Access to the array does not require synchronization on the pool because
|
428
|
+
# the array is newly created and not retained by the pool.
|
429
|
+
#
|
430
|
+
# However; this method bypasses the ConnectionPool's thread-safe connection
|
431
|
+
# access pattern. A returned connection may be owned by another thread,
|
432
|
+
# unowned, or by happen-stance owned by the calling thread.
|
433
|
+
#
|
434
|
+
# Calling methods on a connection without ownership is subject to the
|
435
|
+
# thread-safety guarantees of the underlying method. Many of the methods
|
436
|
+
# on connection adapter classes are inherently multi-thread unsafe.
|
437
|
+
def connections
|
438
|
+
synchronize { @connections.dup }
|
439
|
+
end
|
440
|
+
|
426
441
|
# Disconnects all connections in the pool, and clears the pool.
|
427
442
|
#
|
428
443
|
# Raises:
|
@@ -668,6 +683,10 @@ module ActiveRecord
|
|
668
683
|
thread
|
669
684
|
end
|
670
685
|
|
686
|
+
def current_thread
|
687
|
+
@lock_thread || Thread.current
|
688
|
+
end
|
689
|
+
|
671
690
|
# Take control of all existing connections so a "group" action such as
|
672
691
|
# reload/disconnect can be performed safely. It is no longer enough to
|
673
692
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -915,6 +934,16 @@ module ActiveRecord
|
|
915
934
|
# about the model. The model needs to pass a specification name to the handler,
|
916
935
|
# in order to look up the correct connection pool.
|
917
936
|
class ConnectionHandler
|
937
|
+
def self.create_owner_to_pool # :nodoc:
|
938
|
+
Concurrent::Map.new(initial_capacity: 2) do |h, k|
|
939
|
+
# Discard the parent's connection pools immediately; we have no need
|
940
|
+
# of them
|
941
|
+
discard_unowned_pools(h)
|
942
|
+
|
943
|
+
h[k] = Concurrent::Map.new(initial_capacity: 2)
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
918
947
|
def self.unowned_pool_finalizer(pid_map) # :nodoc:
|
919
948
|
lambda do |_|
|
920
949
|
discard_unowned_pools(pid_map)
|
@@ -929,13 +958,7 @@ module ActiveRecord
|
|
929
958
|
|
930
959
|
def initialize
|
931
960
|
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
932
|
-
@owner_to_pool =
|
933
|
-
# Discard the parent's connection pools immediately; we have no need
|
934
|
-
# of them
|
935
|
-
ConnectionHandler.discard_unowned_pools(h)
|
936
|
-
|
937
|
-
h[k] = Concurrent::Map.new(initial_capacity: 2)
|
938
|
-
end
|
961
|
+
@owner_to_pool = ConnectionHandler.create_owner_to_pool
|
939
962
|
|
940
963
|
# Backup finalizer: if the forked child never needed a pool, the above
|
941
964
|
# early discard has not occurred
|