activerecord 5.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 +228 -0
- data/lib/active_record/association_relation.rb +3 -3
- data/lib/active_record/associations/association.rb +8 -0
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/collection_association.rb +9 -8
- data/lib/active_record/associations/collection_proxy.rb +8 -34
- data/lib/active_record/associations/has_many_association.rb +9 -0
- data/lib/active_record/associations/has_many_through_association.rb +28 -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/attribute_methods/dirty.rb +13 -8
- data/lib/active_record/autosave_association.rb +25 -11
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/collection_cache_key.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +36 -11
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
- 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/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +7 -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 +10 -24
- data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
- 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/enum.rb +1 -0
- data/lib/active_record/errors.rb +18 -12
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/migration.rb +1 -1
- data/lib/active_record/migration/compatibility.rb +15 -15
- data/lib/active_record/model_schema.rb +1 -1
- data/lib/active_record/persistence.rb +5 -4
- data/lib/active_record/querying.rb +1 -1
- data/lib/active_record/railtie.rb +1 -3
- data/lib/active_record/reflection.rb +10 -14
- data/lib/active_record/relation.rb +26 -7
- data/lib/active_record/relation/calculations.rb +16 -12
- data/lib/active_record/relation/delegation.rb +30 -0
- data/lib/active_record/relation/finder_methods.rb +8 -4
- data/lib/active_record/relation/merger.rb +8 -5
- data/lib/active_record/relation/predicate_builder.rb +14 -9
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +35 -10
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/scoping/default.rb +2 -2
- data/lib/active_record/scoping/named.rb +2 -0
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/tasks/database_tasks.rb +1 -1
- data/lib/active_record/transactions.rb +1 -1
- metadata +9 -10
@@ -99,6 +99,7 @@ module ActiveRecord
|
|
99
99
|
def delete_or_nullify_all_records(method)
|
100
100
|
count = delete_count(method, scope)
|
101
101
|
update_counter(-count)
|
102
|
+
count
|
102
103
|
end
|
103
104
|
|
104
105
|
# Deletes the records according to the <tt>:dependent</tt> option.
|
@@ -130,6 +131,14 @@ module ActiveRecord
|
|
130
131
|
end
|
131
132
|
saved_successfully
|
132
133
|
end
|
134
|
+
|
135
|
+
def difference(a, b)
|
136
|
+
a - b
|
137
|
+
end
|
138
|
+
|
139
|
+
def intersection(a, b)
|
140
|
+
a & b
|
141
|
+
end
|
133
142
|
end
|
134
143
|
end
|
135
144
|
end
|
@@ -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,30 @@ module ActiveRecord
|
|
161
154
|
else
|
162
155
|
update_counter(-count)
|
163
156
|
end
|
157
|
+
|
158
|
+
count
|
159
|
+
end
|
160
|
+
|
161
|
+
def difference(a, b)
|
162
|
+
distribution = distribution(b)
|
163
|
+
|
164
|
+
a.reject { |record| mark_occurrence(distribution, record) }
|
165
|
+
end
|
166
|
+
|
167
|
+
def intersection(a, b)
|
168
|
+
distribution = distribution(b)
|
169
|
+
|
170
|
+
a.select { |record| mark_occurrence(distribution, record) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def mark_occurrence(distribution, record)
|
174
|
+
distribution[record] > 0 && distribution[record] -= 1
|
175
|
+
end
|
176
|
+
|
177
|
+
def distribution(array)
|
178
|
+
array.each_with_object(Hash.new(0)) do |record, distribution|
|
179
|
+
distribution[record] += 1
|
180
|
+
end
|
164
181
|
end
|
165
182
|
|
166
183
|
def through_records_for(record)
|
@@ -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!
|
@@ -16,9 +16,6 @@ module ActiveRecord
|
|
16
16
|
|
17
17
|
class_attribute :partial_writes, instance_writer: false, default: true
|
18
18
|
|
19
|
-
after_create { changes_applied }
|
20
|
-
after_update { changes_applied }
|
21
|
-
|
22
19
|
# Attribute methods for "changed in last call to save?"
|
23
20
|
attribute_method_affix(prefix: "saved_change_to_", suffix: "?")
|
24
21
|
attribute_method_prefix("saved_change_to_")
|
@@ -123,18 +120,26 @@ module ActiveRecord
|
|
123
120
|
end
|
124
121
|
|
125
122
|
private
|
126
|
-
def write_attribute_without_type_cast(attr_name,
|
127
|
-
|
128
|
-
|
123
|
+
def write_attribute_without_type_cast(attr_name, value)
|
124
|
+
name = attr_name.to_s
|
125
|
+
if self.class.attribute_alias?(name)
|
126
|
+
name = self.class.attribute_alias(name)
|
127
|
+
end
|
128
|
+
result = super(name, value)
|
129
|
+
clear_attribute_change(name)
|
129
130
|
result
|
130
131
|
end
|
131
132
|
|
132
133
|
def _update_record(*)
|
133
|
-
partial_writes? ? super(keys_for_partial_write) : super
|
134
|
+
affected_rows = partial_writes? ? super(keys_for_partial_write) : super
|
135
|
+
changes_applied
|
136
|
+
affected_rows
|
134
137
|
end
|
135
138
|
|
136
139
|
def _create_record(*)
|
137
|
-
partial_writes? ? super(keys_for_partial_write) : super
|
140
|
+
id = partial_writes? ? super(keys_for_partial_write) : super
|
141
|
+
changes_applied
|
142
|
+
id
|
138
143
|
end
|
139
144
|
|
140
145
|
def keys_for_partial_write
|
@@ -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,30 +382,34 @@ 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) }
|
392
396
|
records -= records_to_destroy
|
393
397
|
end
|
394
398
|
|
395
|
-
records.
|
399
|
+
records.each do |record|
|
396
400
|
next if record.destroyed?
|
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?
|
408
|
+
association_saved = association.insert_record(record)
|
409
|
+
|
404
410
|
if reflection.validate?
|
405
|
-
|
406
|
-
saved =
|
407
|
-
else
|
408
|
-
association.insert_record(record)
|
411
|
+
errors.add(reflection.name) unless association_saved
|
412
|
+
saved = association_saved
|
409
413
|
end
|
410
414
|
end
|
411
415
|
elsif autosave
|
@@ -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!
|
@@ -20,9 +20,9 @@ module ActiveRecord
|
|
20
20
|
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
21
21
|
|
22
22
|
if collection.has_limit_or_offset?
|
23
|
-
query = collection.select(column)
|
23
|
+
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
24
24
|
subquery_alias = "subquery_for_cache_key"
|
25
|
-
subquery_column = "#{subquery_alias}
|
25
|
+
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
|
26
26
|
subquery = query.arel.as(subquery_alias)
|
27
27
|
arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
|
28
28
|
else
|
@@ -188,7 +188,9 @@ module ActiveRecord
|
|
188
188
|
t0 = Time.now
|
189
189
|
elapsed = 0
|
190
190
|
loop do
|
191
|
-
|
191
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
192
|
+
@cond.wait(timeout - elapsed)
|
193
|
+
end
|
192
194
|
|
193
195
|
return remove if any?
|
194
196
|
|
@@ -308,7 +310,7 @@ module ActiveRecord
|
|
308
310
|
include QueryCache::ConnectionPoolConfiguration
|
309
311
|
|
310
312
|
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
311
|
-
attr_reader :spec, :
|
313
|
+
attr_reader :spec, :size, :reaper
|
312
314
|
|
313
315
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
314
316
|
# object which describes database connection information (e.g. adapter,
|
@@ -377,7 +379,7 @@ module ActiveRecord
|
|
377
379
|
# #connection can be called any number of times; the connection is
|
378
380
|
# held in a cache keyed by a thread.
|
379
381
|
def connection
|
380
|
-
@thread_cached_conns[connection_cache_key(
|
382
|
+
@thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
|
381
383
|
end
|
382
384
|
|
383
385
|
# Returns true if there is an open connection being used for the current thread.
|
@@ -386,7 +388,7 @@ module ActiveRecord
|
|
386
388
|
# #connection or #with_connection methods. Connections obtained through
|
387
389
|
# #checkout will not be detected by #active_connection?
|
388
390
|
def active_connection?
|
389
|
-
@thread_cached_conns[connection_cache_key(
|
391
|
+
@thread_cached_conns[connection_cache_key(current_thread)]
|
390
392
|
end
|
391
393
|
|
392
394
|
# Signal that the thread is finished with the current connection.
|
@@ -421,6 +423,21 @@ module ActiveRecord
|
|
421
423
|
synchronize { @connections.any? }
|
422
424
|
end
|
423
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
|
+
|
424
441
|
# Disconnects all connections in the pool, and clears the pool.
|
425
442
|
#
|
426
443
|
# Raises:
|
@@ -666,6 +683,10 @@ module ActiveRecord
|
|
666
683
|
thread
|
667
684
|
end
|
668
685
|
|
686
|
+
def current_thread
|
687
|
+
@lock_thread || Thread.current
|
688
|
+
end
|
689
|
+
|
669
690
|
# Take control of all existing connections so a "group" action such as
|
670
691
|
# reload/disconnect can be performed safely. It is no longer enough to
|
671
692
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
@@ -913,6 +934,16 @@ module ActiveRecord
|
|
913
934
|
# about the model. The model needs to pass a specification name to the handler,
|
914
935
|
# in order to look up the correct connection pool.
|
915
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
|
+
|
916
947
|
def self.unowned_pool_finalizer(pid_map) # :nodoc:
|
917
948
|
lambda do |_|
|
918
949
|
discard_unowned_pools(pid_map)
|
@@ -927,13 +958,7 @@ module ActiveRecord
|
|
927
958
|
|
928
959
|
def initialize
|
929
960
|
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
930
|
-
@owner_to_pool =
|
931
|
-
# Discard the parent's connection pools immediately; we have no need
|
932
|
-
# of them
|
933
|
-
ConnectionHandler.discard_unowned_pools(h)
|
934
|
-
|
935
|
-
h[k] = Concurrent::Map.new(initial_capacity: 2)
|
936
|
-
end
|
961
|
+
@owner_to_pool = ConnectionHandler.create_owner_to_pool
|
937
962
|
|
938
963
|
# Backup finalizer: if the forked child never needed a pool, the above
|
939
964
|
# early discard has not occurred
|
@@ -20,9 +20,22 @@ module ActiveRecord
|
|
20
20
|
raise "Passing bind parameters with an arel AST is forbidden. " \
|
21
21
|
"The values must be stored on the AST directly"
|
22
22
|
end
|
23
|
-
|
24
|
-
|
23
|
+
|
24
|
+
if prepared_statements
|
25
|
+
sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
|
26
|
+
|
27
|
+
if binds.length > bind_params_length
|
28
|
+
unprepared_statement do
|
29
|
+
sql, binds = to_sql_and_binds(arel_or_sql_string)
|
30
|
+
visitor.preparable = false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
else
|
34
|
+
sql = visitor.accept(arel_or_sql_string.ast, collector).value
|
35
|
+
end
|
36
|
+
[sql.freeze, binds]
|
25
37
|
else
|
38
|
+
visitor.preparable = false if prepared_statements
|
26
39
|
[arel_or_sql_string.dup.freeze, binds]
|
27
40
|
end
|
28
41
|
end
|
@@ -46,11 +59,11 @@ module ActiveRecord
|
|
46
59
|
def select_all(arel, name = nil, binds = [], preparable: nil)
|
47
60
|
arel = arel_from_relation(arel)
|
48
61
|
sql, binds = to_sql_and_binds(arel, binds)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
preparable = visitor.preparable
|
62
|
+
|
63
|
+
if preparable.nil?
|
64
|
+
preparable = prepared_statements ? visitor.preparable : false
|
53
65
|
end
|
66
|
+
|
54
67
|
if prepared_statements && preparable
|
55
68
|
select_prepared(sql, name, binds)
|
56
69
|
else
|