activerecord 7.2.1.1 → 8.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -756
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/associations/association.rb +25 -5
  5. data/lib/active_record/associations/builder/association.rb +7 -6
  6. data/lib/active_record/associations/collection_association.rb +10 -8
  7. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  8. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  9. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  10. data/lib/active_record/associations/join_dependency.rb +4 -4
  11. data/lib/active_record/associations/preloader/association.rb +2 -2
  12. data/lib/active_record/associations/singular_association.rb +8 -3
  13. data/lib/active_record/associations.rb +34 -4
  14. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  15. data/lib/active_record/attribute_assignment.rb +9 -1
  16. data/lib/active_record/attribute_methods/primary_key.rb +2 -7
  17. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -12
  18. data/lib/active_record/attributes.rb +1 -2
  19. data/lib/active_record/autosave_association.rb +69 -27
  20. data/lib/active_record/callbacks.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +16 -10
  22. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  23. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
  24. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +26 -9
  25. data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
  26. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -4
  27. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  28. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  29. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
  30. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +34 -7
  31. data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
  32. data/lib/active_record/connection_adapters/abstract_adapter.rb +24 -26
  33. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -42
  34. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  35. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  36. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -45
  37. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +42 -98
  38. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
  39. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +64 -42
  40. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  41. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  42. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  43. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  44. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +0 -11
  45. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -11
  46. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  47. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +54 -14
  48. data/lib/active_record/connection_adapters/postgresql_adapter.rb +45 -97
  49. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  50. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +76 -100
  51. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  52. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +13 -0
  53. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +8 -1
  54. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
  55. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
  56. data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
  57. data/lib/active_record/connection_adapters.rb +0 -56
  58. data/lib/active_record/connection_handling.rb +22 -0
  59. data/lib/active_record/core.rb +28 -18
  60. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  61. data/lib/active_record/encryption/config.rb +3 -1
  62. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  63. data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
  64. data/lib/active_record/encryption/encryptor.rb +15 -8
  65. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  66. data/lib/active_record/encryption/key_provider.rb +1 -1
  67. data/lib/active_record/encryption/scheme.rb +8 -1
  68. data/lib/active_record/encryption.rb +2 -0
  69. data/lib/active_record/enum.rb +54 -75
  70. data/lib/active_record/errors.rb +13 -5
  71. data/lib/active_record/fixtures.rb +0 -2
  72. data/lib/active_record/future_result.rb +14 -10
  73. data/lib/active_record/gem_version.rb +4 -4
  74. data/lib/active_record/insert_all.rb +1 -1
  75. data/lib/active_record/locking/optimistic.rb +1 -1
  76. data/lib/active_record/log_subscriber.rb +5 -11
  77. data/lib/active_record/marshalling.rb +4 -1
  78. data/lib/active_record/migration/command_recorder.rb +22 -5
  79. data/lib/active_record/migration/compatibility.rb +5 -2
  80. data/lib/active_record/migration.rb +35 -38
  81. data/lib/active_record/model_schema.rb +4 -6
  82. data/lib/active_record/nested_attributes.rb +11 -2
  83. data/lib/active_record/persistence.rb +128 -130
  84. data/lib/active_record/query_cache.rb +0 -4
  85. data/lib/active_record/query_logs.rb +102 -50
  86. data/lib/active_record/query_logs_formatter.rb +17 -28
  87. data/lib/active_record/querying.rb +8 -8
  88. data/lib/active_record/railtie.rb +9 -38
  89. data/lib/active_record/railties/databases.rake +1 -1
  90. data/lib/active_record/reflection.rb +23 -23
  91. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  92. data/lib/active_record/relation/batches.rb +132 -72
  93. data/lib/active_record/relation/calculations.rb +41 -40
  94. data/lib/active_record/relation/delegation.rb +25 -14
  95. data/lib/active_record/relation/finder_methods.rb +18 -18
  96. data/lib/active_record/relation/merger.rb +8 -8
  97. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  98. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  99. data/lib/active_record/relation/predicate_builder.rb +14 -1
  100. data/lib/active_record/relation/query_methods.rb +122 -71
  101. data/lib/active_record/relation/spawn_methods.rb +1 -1
  102. data/lib/active_record/relation.rb +79 -61
  103. data/lib/active_record/result.rb +66 -4
  104. data/lib/active_record/sanitization.rb +7 -6
  105. data/lib/active_record/schema_dumper.rb +5 -0
  106. data/lib/active_record/schema_migration.rb +2 -1
  107. data/lib/active_record/scoping/named.rb +5 -2
  108. data/lib/active_record/statement_cache.rb +12 -12
  109. data/lib/active_record/store.rb +7 -3
  110. data/lib/active_record/table_metadata.rb +1 -3
  111. data/lib/active_record/tasks/database_tasks.rb +40 -47
  112. data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
  113. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
  114. data/lib/active_record/test_fixtures.rb +12 -0
  115. data/lib/active_record/testing/query_assertions.rb +2 -2
  116. data/lib/active_record/token_for.rb +1 -1
  117. data/lib/active_record/validations/uniqueness.rb +9 -8
  118. data/lib/active_record.rb +15 -45
  119. data/lib/arel/collectors/bind.rb +1 -1
  120. data/lib/arel/table.rb +3 -7
  121. data/lib/arel/visitors/sqlite.rb +25 -0
  122. metadata +10 -11
  123. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
data/README.rdoc CHANGED
@@ -139,7 +139,7 @@ A short rundown of some of the major features:
139
139
 
140
140
  * Database agnostic schema management with Migrations.
141
141
 
142
- class AddSystemSettings < ActiveRecord::Migration[7.2]
142
+ class AddSystemSettings < ActiveRecord::Migration[8.0]
143
143
  def up
144
144
  create_table :system_settings do |t|
145
145
  t.string :name
@@ -34,7 +34,7 @@ module ActiveRecord
34
34
  # the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
35
35
  class Association # :nodoc:
36
36
  attr_accessor :owner
37
- attr_reader :target, :reflection, :disable_joins
37
+ attr_reader :reflection, :disable_joins
38
38
 
39
39
  delegate :options, to: :reflection
40
40
 
@@ -50,6 +50,13 @@ module ActiveRecord
50
50
  @skip_strict_loading = nil
51
51
  end
52
52
 
53
+ def target
54
+ if @target.is_a?(Promise)
55
+ @target = @target.value
56
+ end
57
+ @target
58
+ end
59
+
53
60
  # Resets the \loaded flag to +false+ and sets the \target to +nil+.
54
61
  def reset
55
62
  @loaded = false
@@ -172,7 +179,7 @@ module ActiveRecord
172
179
  # ActiveRecord::RecordNotFound is rescued within the method, and it is
173
180
  # not reraised. The proxy is \reset and +nil+ is the return value.
174
181
  def load_target
175
- @target = find_target if (@stale_state && stale_target?) || find_target?
182
+ @target = find_target(async: false) if (@stale_state && stale_target?) || find_target?
176
183
 
177
184
  loaded! unless loaded?
178
185
  target
@@ -180,6 +187,13 @@ module ActiveRecord
180
187
  reset
181
188
  end
182
189
 
190
+ def async_load_target # :nodoc:
191
+ @target = find_target(async: true) if (@stale_state && stale_target?) || find_target?
192
+
193
+ loaded! unless loaded?
194
+ nil
195
+ end
196
+
183
197
  # We can't dump @reflection and @through_reflection since it contains the scope proc
184
198
  def marshal_dump
185
199
  ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
@@ -223,13 +237,19 @@ module ActiveRecord
223
237
  klass
224
238
  end
225
239
 
226
- def find_target
240
+ def find_target(async: false)
227
241
  if violates_strict_loading?
228
242
  Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
229
243
  end
230
244
 
231
245
  scope = self.scope
232
- return scope.to_a if skip_statement_cache?(scope)
246
+ if skip_statement_cache?(scope)
247
+ if async
248
+ return scope.load_async.then(&:to_a)
249
+ else
250
+ return scope.to_a
251
+ end
252
+ end
233
253
 
234
254
  sc = reflection.association_scope_cache(klass, owner) do |params|
235
255
  as = AssociationScope.create { params.bind }
@@ -238,7 +258,7 @@ module ActiveRecord
238
258
 
239
259
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
240
260
  klass.with_connection do |c|
241
- sc.execute(binds, c) do |record|
261
+ sc.execute(binds, c, async: async) do |record|
242
262
  set_inverse_instance(record)
243
263
  if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
244
264
  record.strict_loading!
@@ -30,10 +30,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
30
30
  end
31
31
 
32
32
  reflection = create_reflection(model, name, scope, options, &block)
33
- define_accessors model, reflection
34
- define_callbacks model, reflection
35
- define_validations model, reflection
36
- define_change_tracking_methods model, reflection
33
+ define_accessors(model, reflection)
34
+ define_callbacks(model, reflection)
35
+ define_validations(model, reflection)
36
+ define_change_tracking_methods(model, reflection)
37
37
  reflection
38
38
  end
39
39
 
@@ -71,6 +71,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
71
71
  end
72
72
 
73
73
  def self.define_extensions(model, name)
74
+ # noop
74
75
  end
75
76
 
76
77
  def self.define_callbacks(model, reflection)
@@ -81,7 +82,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
81
82
  end
82
83
 
83
84
  Association.extensions.each do |extension|
84
- extension.build model, reflection
85
+ extension.build(model, reflection)
85
86
  end
86
87
  end
87
88
 
@@ -131,7 +132,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
131
132
  err_message = "A valid destroy_association_async_job is required to use `dependent: :destroy_async` on associations"
132
133
  raise ActiveRecord::ConfigurationError, err_message
133
134
  end
134
- unless valid_dependent_options.include? dependent
135
+ unless valid_dependent_options.include?(dependent)
135
136
  raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}"
136
137
  end
137
138
  end
@@ -94,7 +94,7 @@ module ActiveRecord
94
94
  def find(*args)
95
95
  if options[:inverse_of] && loaded?
96
96
  args_flatten = args.flatten
97
- model = scope.klass
97
+ model = scope.model
98
98
 
99
99
  if args_flatten.blank?
100
100
  error_message = "Couldn't find #{model.name} without an ID"
@@ -256,14 +256,16 @@ module ActiveRecord
256
256
  end
257
257
 
258
258
  def include?(record)
259
- if record.is_a?(reflection.klass)
260
- if record.new_record?
261
- include_in_memory?(record)
262
- else
263
- loaded? ? target.include?(record) : scope.exists?(record.id)
264
- end
259
+ klass = reflection.klass
260
+ return false unless record.is_a?(klass)
261
+
262
+ if record.new_record?
263
+ include_in_memory?(record)
264
+ elsif loaded?
265
+ target.include?(record)
265
266
  else
266
- false
267
+ record_id = klass.composite_primary_key? ? klass.primary_key.zip(record.id).to_h : record.id
268
+ scope.exists?(record_id)
267
269
  end
268
270
  end
269
271
 
@@ -47,7 +47,7 @@ module ActiveRecord
47
47
  end
48
48
 
49
49
  if scope.order_values.empty? && ordered
50
- split_scope = DisableJoinsAssociationRelation.create(scope.klass, key, join_ids)
50
+ split_scope = DisableJoinsAssociationRelation.create(scope.model, key, join_ids)
51
51
  split_scope.where_clause += scope.where_clause
52
52
  split_scope
53
53
  else
@@ -93,7 +93,13 @@ module ActiveRecord
93
93
  @through_scope = scope
94
94
  record = super
95
95
 
96
- inverse = source_reflection.inverse_of
96
+ inverse =
97
+ if source_reflection.polymorphic?
98
+ source_reflection.polymorphic_inverse_of(record.class)
99
+ else
100
+ source_reflection.inverse_of
101
+ end
102
+
97
103
  if inverse
98
104
  if inverse.collection?
99
105
  record.send(inverse.name) << build_through_record(record)
@@ -140,7 +146,7 @@ module ActiveRecord
140
146
 
141
147
  case method
142
148
  when :destroy
143
- if scope.klass.primary_key
149
+ if scope.model.primary_key
144
150
  count = scope.destroy_all.count(&:destroyed?)
145
151
  else
146
152
  scope.each(&:_run_destroy_callbacks)
@@ -216,7 +222,8 @@ module ActiveRecord
216
222
  end
217
223
  end
218
224
 
219
- def find_target
225
+ def find_target(async: false)
226
+ raise NotImplementedError, "No async loading for HasManyThroughAssociation yet" if async
220
227
  return [] unless target_reflection_has_associated_record?
221
228
  return scope.to_a if disable_joins
222
229
  super
@@ -25,8 +25,9 @@ module ActiveRecord
25
25
  joins = []
26
26
  chain = []
27
27
 
28
- reflection.chain.each do |reflection|
29
- table, terminated = yield reflection
28
+ reflection_chain = reflection.chain
29
+ reflection_chain.each_with_index do |reflection, index|
30
+ table, terminated = yield reflection, reflection_chain[index..]
30
31
  @table ||= table
31
32
 
32
33
  if terminated
@@ -190,12 +190,12 @@ module ActiveRecord
190
190
  def make_constraints(parent, child, join_type)
191
191
  foreign_table = parent.table
192
192
  foreign_klass = parent.base_klass
193
- child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection|
194
- table, terminated = @joined_tables[reflection]
193
+ child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) do |reflection, remaining_reflection_chain|
194
+ table, terminated = @joined_tables[remaining_reflection_chain]
195
195
  root = reflection == child.reflection
196
196
 
197
197
  if table && (!root || !terminated)
198
- @joined_tables[reflection] = [table, root] if root
198
+ @joined_tables[remaining_reflection_chain] = [table, root] if root
199
199
  next table, true
200
200
  end
201
201
 
@@ -206,7 +206,7 @@ module ActiveRecord
206
206
  root ? name : "#{name}_join"
207
207
  end
208
208
 
209
- @joined_tables[reflection] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
209
+ @joined_tables[remaining_reflection_chain] ||= [table, root] if join_type == Arel::Nodes::OuterJoin
210
210
  table
211
211
  end.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
212
212
  end
@@ -17,12 +17,12 @@ module ActiveRecord
17
17
  def eql?(other)
18
18
  association_key_name == other.association_key_name &&
19
19
  scope.table_name == other.scope.table_name &&
20
- scope.connection_specification_name == other.scope.connection_specification_name &&
20
+ scope.model.connection_specification_name == other.scope.model.connection_specification_name &&
21
21
  scope.values_for_queries == other.scope.values_for_queries
22
22
  end
23
23
 
24
24
  def hash
25
- [association_key_name, scope.table_name, scope.connection_specification_name, scope.values_for_queries].hash
25
+ [association_key_name, scope.model.table_name, scope.model.connection_specification_name, scope.values_for_queries].hash
26
26
  end
27
27
 
28
28
  def records_for(loaders)
@@ -18,6 +18,7 @@ module ActiveRecord
18
18
  def reset
19
19
  super
20
20
  @target = nil
21
+ @future_target = nil
21
22
  end
22
23
 
23
24
  # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
@@ -43,11 +44,15 @@ module ActiveRecord
43
44
  super.except!(*Array(klass.primary_key))
44
45
  end
45
46
 
46
- def find_target
47
+ def find_target(async: false)
47
48
  if disable_joins
48
- scope.first
49
+ if async
50
+ scope.load_async.then(&:first)
51
+ else
52
+ scope.first
53
+ end
49
54
  else
50
- super.first
55
+ super.then(&:first)
51
56
  end
52
57
  end
53
58
 
@@ -379,21 +379,43 @@ module ActiveRecord
379
379
  # after_add: :congratulate_client,
380
380
  # after_remove: :log_after_remove
381
381
  #
382
- # def congratulate_client(record)
382
+ # def congratulate_client(client)
383
383
  # # ...
384
384
  # end
385
385
  #
386
- # def log_after_remove(record)
386
+ # def log_after_remove(client)
387
387
  # # ...
388
388
  # end
389
389
  # end
390
390
  #
391
+ # Callbacks can be defined in three ways:
392
+ #
393
+ # 1. A symbol that references a method defined on the class with the
394
+ # associated collection. For example, <tt>after_add: :congratulate_client</tt>
395
+ # invokes <tt>Firm#congratulate_client(client)</tt>.
396
+ # 2. A callable with a signature that accepts both the record with the
397
+ # associated collection and the record being added or removed. For
398
+ # example, <tt>after_add: ->(firm, client) { ... }</tt>.
399
+ # 3. An object that responds to the callback name. For example, passing
400
+ # <tt>after_add: CallbackObject.new</tt> invokes <tt>CallbackObject#after_add(firm,
401
+ # client)</tt>.
402
+ #
391
403
  # It's possible to stack callbacks by passing them as an array. Example:
392
404
  #
405
+ # class CallbackObject
406
+ # def after_add(firm, client)
407
+ # firm.log << "after_adding #{client.id}"
408
+ # end
409
+ # end
410
+ #
393
411
  # class Firm < ActiveRecord::Base
394
412
  # has_many :clients,
395
413
  # dependent: :destroy,
396
- # after_add: [:congratulate_client, -> (firm, record) { firm.log << "after_adding#{record.id}" }],
414
+ # after_add: [
415
+ # :congratulate_client,
416
+ # -> (firm, client) { firm.log << "after_adding #{client.id}" },
417
+ # CallbackObject.new
418
+ # ],
397
419
  # after_remove: :log_after_remove
398
420
  # end
399
421
  #
@@ -1251,6 +1273,14 @@ module ActiveRecord
1251
1273
  # persisted new records placed at the end.
1252
1274
  # When set to +:nested_attributes_order+, the index is based on the record order received by
1253
1275
  # nested attributes setter, when accepts_nested_attributes_for is used.
1276
+ # [:before_add]
1277
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is added</b> to the association collection.
1278
+ # [:after_add]
1279
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>after an object is added</b> to the association collection.
1280
+ # [:before_remove]
1281
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>before an object is removed</b> from the association collection.
1282
+ # [:after_remove]
1283
+ # Defines an {association callback}[rdoc-ref:Associations::ClassMethods@Association+callbacks] that gets triggered <b>after an object is removed</b> from the association collection.
1254
1284
  #
1255
1285
  # Option examples:
1256
1286
  # has_many :comments, -> { order("posted_on") }
@@ -1666,7 +1696,7 @@ module ActiveRecord
1666
1696
  # The join table should not have a primary key or a model associated with it. You must manually generate the
1667
1697
  # join table with a migration such as this:
1668
1698
  #
1669
- # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[7.2]
1699
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[8.0]
1670
1700
  # def change
1671
1701
  # create_join_table :developers, :projects
1672
1702
  # end
@@ -1,29 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/atomic/atomic_boolean"
4
+ require "concurrent/atomic/read_write_lock"
5
+
3
6
  module ActiveRecord
4
7
  class AsynchronousQueriesTracker # :nodoc:
5
- module NullSession # :nodoc:
6
- class << self
7
- def active?
8
- true
9
- end
10
-
11
- def finalize
12
- end
13
- end
14
- end
15
-
16
8
  class Session # :nodoc:
17
9
  def initialize
18
- @active = true
10
+ @active = Concurrent::AtomicBoolean.new(true)
11
+ @lock = Concurrent::ReadWriteLock.new
19
12
  end
20
13
 
21
14
  def active?
22
- @active
15
+ @active.true?
23
16
  end
24
17
 
25
- def finalize
26
- @active = false
18
+ def synchronize(&block)
19
+ @lock.with_read_lock(&block)
20
+ end
21
+
22
+ def finalize(wait = false)
23
+ @active.make_false
24
+ if wait
25
+ # Wait until all thread with a read lock are done
26
+ @lock.with_write_lock { }
27
+ end
27
28
  end
28
29
  end
29
30
 
@@ -33,7 +34,7 @@ module ActiveRecord
33
34
  end
34
35
 
35
36
  def run
36
- ActiveRecord::Base.asynchronous_queries_tracker.start_session
37
+ ActiveRecord::Base.asynchronous_queries_tracker.tap(&:start_session)
37
38
  end
38
39
 
39
40
  def complete(asynchronous_queries_tracker)
@@ -41,20 +42,23 @@ module ActiveRecord
41
42
  end
42
43
  end
43
44
 
44
- attr_reader :current_session
45
-
46
45
  def initialize
47
- @current_session = NullSession
46
+ @stack = []
47
+ end
48
+
49
+ def current_session
50
+ @stack.last or raise ActiveRecordError, "Can't perform asynchronous queries without a query session"
48
51
  end
49
52
 
50
53
  def start_session
51
- @current_session = Session.new
52
- self
54
+ session = Session.new
55
+ @stack << session
53
56
  end
54
57
 
55
- def finalize_session
56
- @current_session.finalize
57
- @current_session = NullSession
58
+ def finalize_session(wait = false)
59
+ session = @stack.pop
60
+ session&.finalize(wait)
61
+ self
58
62
  end
59
63
  end
60
64
  end
@@ -4,21 +4,29 @@ module ActiveRecord
4
4
  module AttributeAssignment
5
5
  private
6
6
  def _assign_attributes(attributes)
7
- multi_parameter_attributes = nil
7
+ multi_parameter_attributes = nested_parameter_attributes = nil
8
8
 
9
9
  attributes.each do |k, v|
10
10
  key = k.to_s
11
11
 
12
12
  if key.include?("(")
13
13
  (multi_parameter_attributes ||= {})[key] = v
14
+ elsif v.is_a?(Hash)
15
+ (nested_parameter_attributes ||= {})[key] = v
14
16
  else
15
17
  _assign_attribute(key, v)
16
18
  end
17
19
  end
18
20
 
21
+ assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
19
22
  assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
20
23
  end
21
24
 
25
+ # Assign any deferred nested attributes after the base attributes have been set.
26
+ def assign_nested_parameter_attributes(pairs)
27
+ pairs.each { |k, v| _assign_attribute(k, v) }
28
+ end
29
+
22
30
  # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
23
31
  # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
24
32
  # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module ActiveRecord
6
4
  module AttributeMethods
7
5
  # = Active Record Attribute Methods Primary Key
@@ -89,10 +87,9 @@ module ActiveRecord
89
87
  @composite_primary_key
90
88
  end
91
89
 
92
- # Returns a quoted version of the primary key name, used to construct
93
- # SQL statements.
90
+ # Returns a quoted version of the primary key name.
94
91
  def quoted_primary_key
95
- @quoted_primary_key ||= adapter_class.quote_column_name(primary_key)
92
+ adapter_class.quote_column_name(primary_key)
96
93
  end
97
94
 
98
95
  def reset_primary_key # :nodoc:
@@ -138,7 +135,6 @@ module ActiveRecord
138
135
  elsif value
139
136
  -value.to_s
140
137
  end
141
- @quoted_primary_key = nil
142
138
  @attributes_builder = nil
143
139
  end
144
140
 
@@ -148,7 +144,6 @@ module ActiveRecord
148
144
  base.class_eval do
149
145
  @primary_key = PRIMARY_KEY_NOT_SET
150
146
  @composite_primary_key = false
151
- @quoted_primary_key = nil
152
147
  @attributes_builder = nil
153
148
  end
154
149
  end
@@ -28,10 +28,14 @@ module ActiveRecord
28
28
  elsif value.respond_to?(:infinite?) && value.infinite?
29
29
  value
30
30
  else
31
- map_avoiding_infinite_recursion(super) { |v| cast(v) }
31
+ map(super) { |v| cast(v) }
32
32
  end
33
33
  end
34
34
 
35
+ def ==(other)
36
+ other.is_a?(self.class) && __getobj__ == other.__getobj__
37
+ end
38
+
35
39
  private
36
40
  def convert_time_to_time_zone(value)
37
41
  return if value.nil?
@@ -41,23 +45,13 @@ module ActiveRecord
41
45
  elsif value.respond_to?(:infinite?) && value.infinite?
42
46
  value
43
47
  else
44
- map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
48
+ map(value) { |v| convert_time_to_time_zone(v) }
45
49
  end
46
50
  end
47
51
 
48
52
  def set_time_zone_without_conversion(value)
49
53
  ::Time.zone.local_to_utc(value).try(:in_time_zone) if value
50
54
  end
51
-
52
- def map_avoiding_infinite_recursion(value)
53
- map(value) do |v|
54
- if value.equal?(v)
55
- nil
56
- else
57
- yield(v)
58
- end
59
- end
60
- end
61
55
  end
62
56
 
63
57
  extend ActiveSupport::Concern
@@ -212,8 +212,7 @@ module ActiveRecord
212
212
  #--
213
213
  # Implemented by ActiveModel::AttributeRegistration#attribute.
214
214
 
215
- # This is the low level API which sits beneath +attribute+. It only
216
- # accepts type objects, and will do its work immediately instead of
215
+ # This API only accepts type objects, and will do its work immediately instead of
217
216
  # waiting for the schema to load. While this method
218
217
  # is provided so it can be used by plugin authors, application code
219
218
  # should probably use ClassMethods#attribute.