activerecord 5.2.0 → 5.2.1.rc1
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 +65 -0
- data/lib/active_record/associations.rb +9 -9
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +17 -10
- data/lib/active_record/associations/belongs_to_association.rb +14 -5
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
- data/lib/active_record/associations/builder/belongs_to.rb +11 -2
- data/lib/active_record/associations/collection_association.rb +10 -7
- data/lib/active_record/associations/has_many_through_association.rb +1 -1
- data/lib/active_record/associations/has_one_association.rb +8 -0
- data/lib/active_record/associations/has_one_through_association.rb +5 -1
- data/lib/active_record/associations/join_dependency.rb +39 -64
- data/lib/active_record/associations/join_dependency/join_association.rb +12 -18
- data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
- data/lib/active_record/associations/singular_association.rb +4 -10
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +1 -1
- data/lib/active_record/autosave_association.rb +8 -3
- data/lib/active_record/callbacks.rb +4 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +3 -12
- data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +0 -11
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +0 -3
- data/lib/active_record/counter_cache.rb +17 -13
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/persistence.rb +1 -1
- data/lib/active_record/query_cache.rb +4 -11
- data/lib/active_record/relation.rb +13 -13
- data/lib/active_record/relation/finder_methods.rb +2 -4
- data/lib/active_record/relation/merger.rb +2 -6
- data/lib/active_record/relation/predicate_builder.rb +6 -5
- data/lib/active_record/relation/query_methods.rb +16 -13
- data/lib/active_record/timestamp.rb +8 -1
- data/lib/active_record/transactions.rb +23 -20
- data/lib/active_record/type/serialized.rb +4 -0
- metadata +11 -11
@@ -7,6 +7,10 @@ module ActiveRecord
|
|
7
7
|
# Returns an array of indexes for the given table.
|
8
8
|
def indexes(table_name)
|
9
9
|
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
|
10
|
+
# Indexes SQLite creates implicitly for internal use start with "sqlite_".
|
11
|
+
# See https://www.sqlite.org/fileformat2.html#intschema
|
12
|
+
next if row["name"].starts_with?("sqlite_")
|
13
|
+
|
10
14
|
index_sql = query_value(<<-SQL, "SCHEMA")
|
11
15
|
SELECT sql
|
12
16
|
FROM sqlite_master
|
@@ -40,7 +44,7 @@ module ActiveRecord
|
|
40
44
|
where: where,
|
41
45
|
orders: orders
|
42
46
|
)
|
43
|
-
end
|
47
|
+
end.compact
|
44
48
|
end
|
45
49
|
|
46
50
|
def create_schema_dumper(options)
|
@@ -453,9 +453,6 @@ module ActiveRecord
|
|
453
453
|
def copy_table_indexes(from, to, rename = {})
|
454
454
|
indexes(from).each do |index|
|
455
455
|
name = index.name
|
456
|
-
# indexes sqlite creates for internal use start with `sqlite_` and
|
457
|
-
# don't need to be copied
|
458
|
-
next if name.starts_with?("sqlite_")
|
459
456
|
if to == "a#{from}"
|
460
457
|
name = "t#{name}"
|
461
458
|
elsif from == "a#{to}"
|
@@ -47,8 +47,12 @@ module ActiveRecord
|
|
47
47
|
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
|
48
48
|
counter_name = reflection.counter_cache_column
|
49
49
|
|
50
|
-
updates = { counter_name
|
51
|
-
|
50
|
+
updates = { counter_name => object.send(counter_association).count(:all) }
|
51
|
+
|
52
|
+
if touch
|
53
|
+
names = touch if touch != true
|
54
|
+
updates.merge!(touch_attributes_with_time(*names))
|
55
|
+
end
|
52
56
|
|
53
57
|
unscoped.where(primary_key => object.id).update_all(updates)
|
54
58
|
end
|
@@ -68,8 +72,8 @@ module ActiveRecord
|
|
68
72
|
# * +counters+ - A Hash containing the names of the fields
|
69
73
|
# to update as keys and the amount to update the field by as values.
|
70
74
|
# * <tt>:touch</tt> option - Touch timestamp columns when updating.
|
71
|
-
#
|
72
|
-
#
|
75
|
+
# If attribute names are passed, they are updated along with updated_at/on
|
76
|
+
# attributes.
|
73
77
|
#
|
74
78
|
# ==== Examples
|
75
79
|
#
|
@@ -107,11 +111,18 @@ module ActiveRecord
|
|
107
111
|
end
|
108
112
|
|
109
113
|
if touch
|
110
|
-
|
114
|
+
names = touch if touch != true
|
115
|
+
touch_updates = touch_attributes_with_time(*names)
|
111
116
|
updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
|
112
117
|
end
|
113
118
|
|
114
|
-
|
119
|
+
if id.is_a?(Relation) && self == id.klass
|
120
|
+
relation = id
|
121
|
+
else
|
122
|
+
relation = unscoped.where!(primary_key => id)
|
123
|
+
end
|
124
|
+
|
125
|
+
relation.update_all updates.join(", ")
|
115
126
|
end
|
116
127
|
|
117
128
|
# Increment a numeric field by one, via a direct SQL update.
|
@@ -165,13 +176,6 @@ module ActiveRecord
|
|
165
176
|
def decrement_counter(counter_name, id, touch: nil)
|
166
177
|
update_counters(id, counter_name => -1, touch: touch)
|
167
178
|
end
|
168
|
-
|
169
|
-
private
|
170
|
-
def touch_updates(touch)
|
171
|
-
touch = timestamp_attributes_for_update_in_model if touch == true
|
172
|
-
touch_time = current_time_from_proper_timezone
|
173
|
-
Array(touch).map { |column| [ column, touch_time ] }.to_h
|
174
|
-
end
|
175
179
|
end
|
176
180
|
|
177
181
|
private
|
@@ -373,7 +373,7 @@ module ActiveRecord
|
|
373
373
|
became = klass.allocate
|
374
374
|
became.send(:initialize)
|
375
375
|
became.instance_variable_set("@attributes", @attributes)
|
376
|
-
became.instance_variable_set("@mutations_from_database", @mutations_from_database
|
376
|
+
became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil)
|
377
377
|
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
|
378
378
|
became.instance_variable_set("@new_record", new_record?)
|
379
379
|
became.instance_variable_set("@destroyed", destroyed?)
|
@@ -26,19 +26,12 @@ module ActiveRecord
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def self.run
|
29
|
-
ActiveRecord::Base.connection_handler.connection_pool_list.
|
30
|
-
|
31
|
-
|
32
|
-
pool.enable_query_cache!
|
33
|
-
|
34
|
-
[pool, caching_was_enabled]
|
35
|
-
end
|
29
|
+
ActiveRecord::Base.connection_handler.connection_pool_list.
|
30
|
+
reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! }
|
36
31
|
end
|
37
32
|
|
38
|
-
def self.complete(
|
39
|
-
|
40
|
-
pool.disable_query_cache! unless caching_was_enabled
|
41
|
-
end
|
33
|
+
def self.complete(pools)
|
34
|
+
pools.each { |pool| pool.disable_query_cache! }
|
42
35
|
|
43
36
|
ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool|
|
44
37
|
pool.release_connection if pool.active_connection? && !pool.connection.transaction_open?
|
@@ -277,10 +277,10 @@ module ActiveRecord
|
|
277
277
|
# Please check unscoped if you want to remove all previous scopes (including
|
278
278
|
# the default_scope) during the execution of a block.
|
279
279
|
def scoping
|
280
|
-
previous, klass.current_scope = klass.current_scope(true), self
|
280
|
+
previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass
|
281
281
|
yield
|
282
282
|
ensure
|
283
|
-
klass.current_scope = previous
|
283
|
+
klass.current_scope = previous unless @delegate_to_klass
|
284
284
|
end
|
285
285
|
|
286
286
|
def _exec_scope(*args, &block) # :nodoc:
|
@@ -436,17 +436,16 @@ module ActiveRecord
|
|
436
436
|
# # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
|
437
437
|
def to_sql
|
438
438
|
@to_sql ||= begin
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
end
|
439
|
+
if eager_loading?
|
440
|
+
apply_join_dependency do |relation, join_dependency|
|
441
|
+
relation = join_dependency.apply_column_aliases(relation)
|
442
|
+
relation.to_sql
|
443
|
+
end
|
444
|
+
else
|
445
|
+
conn = klass.connection
|
446
|
+
conn.unprepared_statement { conn.to_sql(arel) }
|
447
|
+
end
|
448
|
+
end
|
450
449
|
end
|
451
450
|
|
452
451
|
# Returns a hash of where conditions.
|
@@ -546,6 +545,7 @@ module ActiveRecord
|
|
546
545
|
if ActiveRecord::NullRelation === relation
|
547
546
|
[]
|
548
547
|
else
|
548
|
+
relation = join_dependency.apply_column_aliases(relation)
|
549
549
|
rows = connection.select_all(relation.arel, "SQL")
|
550
550
|
join_dependency.instantiate(rows, &block)
|
551
551
|
end.freeze
|
@@ -373,13 +373,12 @@ module ActiveRecord
|
|
373
373
|
|
374
374
|
def construct_join_dependency
|
375
375
|
including = eager_load_values + includes_values
|
376
|
-
joins = joins_values.select { |join| join.is_a?(Arel::Nodes::Join) }
|
377
376
|
ActiveRecord::Associations::JoinDependency.new(
|
378
|
-
klass, table, including
|
377
|
+
klass, table, including
|
379
378
|
)
|
380
379
|
end
|
381
380
|
|
382
|
-
def apply_join_dependency(eager_loading:
|
381
|
+
def apply_join_dependency(eager_loading: group_values.empty?)
|
383
382
|
join_dependency = construct_join_dependency
|
384
383
|
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
|
385
384
|
|
@@ -392,7 +391,6 @@ module ActiveRecord
|
|
392
391
|
end
|
393
392
|
|
394
393
|
if block_given?
|
395
|
-
relation._select!(join_dependency.aliases.columns)
|
396
394
|
yield relation, join_dependency
|
397
395
|
else
|
398
396
|
relation
|
@@ -117,13 +117,11 @@ module ActiveRecord
|
|
117
117
|
if other.klass == relation.klass
|
118
118
|
relation.joins!(*other.joins_values)
|
119
119
|
else
|
120
|
-
alias_tracker = nil
|
121
120
|
joins_dependency = other.joins_values.map do |join|
|
122
121
|
case join
|
123
122
|
when Hash, Symbol, Array
|
124
|
-
alias_tracker ||= other.alias_tracker
|
125
123
|
ActiveRecord::Associations::JoinDependency.new(
|
126
|
-
other.klass, other.table, join
|
124
|
+
other.klass, other.table, join
|
127
125
|
)
|
128
126
|
else
|
129
127
|
join
|
@@ -140,13 +138,11 @@ module ActiveRecord
|
|
140
138
|
if other.klass == relation.klass
|
141
139
|
relation.left_outer_joins!(*other.left_outer_joins_values)
|
142
140
|
else
|
143
|
-
alias_tracker = nil
|
144
141
|
joins_dependency = other.left_outer_joins_values.map do |join|
|
145
142
|
case join
|
146
143
|
when Hash, Symbol, Array
|
147
|
-
alias_tracker ||= other.alias_tracker
|
148
144
|
ActiveRecord::Associations::JoinDependency.new(
|
149
|
-
other.klass, other.table, join
|
145
|
+
other.klass, other.table, join
|
150
146
|
)
|
151
147
|
else
|
152
148
|
join
|
@@ -48,7 +48,12 @@ module ActiveRecord
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def build(attribute, value)
|
51
|
-
|
51
|
+
if table.type(attribute.name).force_equality?(value)
|
52
|
+
bind = build_bind_attribute(attribute.name, value)
|
53
|
+
attribute.eq(bind)
|
54
|
+
else
|
55
|
+
handler_for(value).call(attribute, value)
|
56
|
+
end
|
52
57
|
end
|
53
58
|
|
54
59
|
def build_bind_attribute(column_name, value)
|
@@ -98,10 +103,6 @@ module ActiveRecord
|
|
98
103
|
end.reduce(&:and)
|
99
104
|
end
|
100
105
|
queries.reduce(&:or)
|
101
|
-
# FIXME: Deprecate this and provide a public API to force equality
|
102
|
-
elsif (value.is_a?(Range) || value.is_a?(Array)) &&
|
103
|
-
table.type(key.to_s).respond_to?(:subtype)
|
104
|
-
BasicObjectHandler.new(self).call(table.arel_attribute(key), value)
|
105
106
|
else
|
106
107
|
build(table.arel_attribute(key), value)
|
107
108
|
end
|
@@ -893,8 +893,8 @@ module ActiveRecord
|
|
893
893
|
self
|
894
894
|
end
|
895
895
|
|
896
|
-
def skip_query_cache! # :nodoc:
|
897
|
-
self.skip_query_cache_value =
|
896
|
+
def skip_query_cache!(value = true) # :nodoc:
|
897
|
+
self.skip_query_cache_value = value
|
898
898
|
self
|
899
899
|
end
|
900
900
|
|
@@ -903,11 +903,12 @@ module ActiveRecord
|
|
903
903
|
@arel ||= build_arel(aliases)
|
904
904
|
end
|
905
905
|
|
906
|
+
# Returns a relation value with a given name
|
907
|
+
def get_value(name) # :nodoc:
|
908
|
+
@values.fetch(name, DEFAULT_VALUES[name])
|
909
|
+
end
|
910
|
+
|
906
911
|
protected
|
907
|
-
# Returns a relation value with a given name
|
908
|
-
def get_value(name) # :nodoc:
|
909
|
-
@values.fetch(name, DEFAULT_VALUES[name])
|
910
|
-
end
|
911
912
|
|
912
913
|
# Sets the relation value with the given name
|
913
914
|
def set_value(name, value) # :nodoc:
|
@@ -1011,19 +1012,19 @@ module ActiveRecord
|
|
1011
1012
|
def build_join_query(manager, buckets, join_type, aliases)
|
1012
1013
|
buckets.default = []
|
1013
1014
|
|
1014
|
-
association_joins
|
1015
|
-
|
1016
|
-
join_nodes
|
1017
|
-
string_joins
|
1015
|
+
association_joins = buckets[:association_join]
|
1016
|
+
stashed_joins = buckets[:stashed_join]
|
1017
|
+
join_nodes = buckets[:join_node].uniq
|
1018
|
+
string_joins = buckets[:string_join].map(&:strip).uniq
|
1018
1019
|
|
1019
1020
|
join_list = join_nodes + convert_join_strings_to_ast(string_joins)
|
1020
1021
|
alias_tracker = alias_tracker(join_list, aliases)
|
1021
1022
|
|
1022
1023
|
join_dependency = ActiveRecord::Associations::JoinDependency.new(
|
1023
|
-
klass, table, association_joins
|
1024
|
+
klass, table, association_joins
|
1024
1025
|
)
|
1025
1026
|
|
1026
|
-
joins = join_dependency.join_constraints(
|
1027
|
+
joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
|
1027
1028
|
joins.each { |join| manager.from(join) }
|
1028
1029
|
|
1029
1030
|
manager.join_sources.concat(join_list)
|
@@ -1049,11 +1050,13 @@ module ActiveRecord
|
|
1049
1050
|
end
|
1050
1051
|
|
1051
1052
|
def arel_columns(columns)
|
1052
|
-
columns.
|
1053
|
+
columns.flat_map do |field|
|
1053
1054
|
if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value
|
1054
1055
|
arel_attribute(field)
|
1055
1056
|
elsif Symbol === field
|
1056
1057
|
connection.quote_table_name(field.to_s)
|
1058
|
+
elsif Proc === field
|
1059
|
+
field.call
|
1057
1060
|
else
|
1058
1061
|
field
|
1059
1062
|
end
|
@@ -52,7 +52,14 @@ module ActiveRecord
|
|
52
52
|
clear_timestamp_attributes
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
module ClassMethods # :nodoc:
|
56
|
+
def touch_attributes_with_time(*names, time: nil)
|
57
|
+
attribute_names = timestamp_attributes_for_update_in_model
|
58
|
+
attribute_names |= names.map(&:to_s)
|
59
|
+
time ||= current_time_from_proper_timezone
|
60
|
+
attribute_names.each_with_object({}) { |attr_name, result| result[attr_name] = time }
|
61
|
+
end
|
62
|
+
|
56
63
|
private
|
57
64
|
def timestamp_attributes_for_create_in_model
|
58
65
|
timestamp_attributes_for_create.select { |c| column_names.include?(c) }
|
@@ -340,11 +340,13 @@ module ActiveRecord
|
|
340
340
|
# Ensure that it is not called if the object was never persisted (failed create),
|
341
341
|
# but call it after the commit of a destroyed object.
|
342
342
|
def committed!(should_run_callbacks: true) #:nodoc:
|
343
|
-
if should_run_callbacks && destroyed? || persisted?
|
343
|
+
if should_run_callbacks && (destroyed? || persisted?)
|
344
|
+
@_committed_already_called = true
|
344
345
|
_run_commit_without_transaction_enrollment_callbacks
|
345
346
|
_run_commit_callbacks
|
346
347
|
end
|
347
348
|
ensure
|
349
|
+
@_committed_already_called = false
|
348
350
|
force_clear_transaction_record_state
|
349
351
|
end
|
350
352
|
|
@@ -382,13 +384,7 @@ module ActiveRecord
|
|
382
384
|
status = nil
|
383
385
|
self.class.transaction do
|
384
386
|
add_to_transaction
|
385
|
-
|
386
|
-
status = yield
|
387
|
-
rescue ActiveRecord::Rollback
|
388
|
-
clear_transaction_record_state
|
389
|
-
status = nil
|
390
|
-
end
|
391
|
-
|
387
|
+
status = yield
|
392
388
|
raise ActiveRecord::Rollback unless status
|
393
389
|
end
|
394
390
|
status
|
@@ -398,17 +394,29 @@ module ActiveRecord
|
|
398
394
|
end
|
399
395
|
end
|
400
396
|
|
397
|
+
protected
|
398
|
+
attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
|
399
|
+
|
401
400
|
private
|
402
401
|
|
403
402
|
# Save the new record state and id of a record so it can be restored later if a transaction fails.
|
404
403
|
def remember_transaction_record_state
|
405
|
-
@_start_transaction_state[:id] = id
|
406
404
|
@_start_transaction_state.reverse_merge!(
|
405
|
+
id: id,
|
407
406
|
new_record: @new_record,
|
408
407
|
destroyed: @destroyed,
|
409
408
|
frozen?: frozen?,
|
410
409
|
)
|
411
410
|
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
|
411
|
+
remember_new_record_before_last_commit
|
412
|
+
end
|
413
|
+
|
414
|
+
def remember_new_record_before_last_commit
|
415
|
+
if _committed_already_called
|
416
|
+
@_new_record_before_last_commit = false
|
417
|
+
else
|
418
|
+
@_new_record_before_last_commit = @_start_transaction_state[:new_record]
|
419
|
+
end
|
412
420
|
end
|
413
421
|
|
414
422
|
# Clear the new record state and id of a record.
|
@@ -440,22 +448,16 @@ module ActiveRecord
|
|
440
448
|
end
|
441
449
|
end
|
442
450
|
|
443
|
-
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
|
444
|
-
def transaction_record_state(state)
|
445
|
-
@_start_transaction_state[state]
|
446
|
-
end
|
447
|
-
|
448
451
|
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
|
449
452
|
def transaction_include_any_action?(actions)
|
450
453
|
actions.any? do |action|
|
451
454
|
case action
|
452
455
|
when :create
|
453
|
-
|
454
|
-
when :destroy
|
455
|
-
defined?(@_trigger_destroy_callback) && @_trigger_destroy_callback
|
456
|
+
persisted? && @_new_record_before_last_commit
|
456
457
|
when :update
|
457
|
-
!(
|
458
|
-
|
458
|
+
!(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback
|
459
|
+
when :destroy
|
460
|
+
_trigger_destroy_callback
|
459
461
|
end
|
460
462
|
end
|
461
463
|
end
|
@@ -491,7 +493,8 @@ module ActiveRecord
|
|
491
493
|
|
492
494
|
def update_attributes_from_transaction_state(transaction_state)
|
493
495
|
if transaction_state && transaction_state.finalized?
|
494
|
-
restore_transaction_record_state if transaction_state.rolledback?
|
496
|
+
restore_transaction_record_state(transaction_state.fully_rolledback?) if transaction_state.rolledback?
|
497
|
+
force_clear_transaction_record_state if transaction_state.fully_committed?
|
495
498
|
clear_transaction_record_state if transaction_state.fully_completed?
|
496
499
|
end
|
497
500
|
end
|