activerecord 8.0.3 → 8.1.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 (160) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -514
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +2 -0
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_proxy.rb +22 -4
  13. data/lib/active_record/associations/deprecation.rb +88 -0
  14. data/lib/active_record/associations/errors.rb +3 -0
  15. data/lib/active_record/associations/join_dependency.rb +2 -0
  16. data/lib/active_record/associations/preloader/branch.rb +1 -0
  17. data/lib/active_record/associations.rb +159 -21
  18. data/lib/active_record/attribute_methods/serialization.rb +16 -3
  19. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  20. data/lib/active_record/attributes.rb +3 -0
  21. data/lib/active_record/autosave_association.rb +1 -1
  22. data/lib/active_record/base.rb +0 -2
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +85 -22
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -2
  45. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  46. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
  47. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  48. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  50. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
  51. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  52. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
  53. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
  54. data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
  55. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  56. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
  57. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  58. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  59. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
  60. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  61. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  62. data/lib/active_record/connection_adapters.rb +1 -0
  63. data/lib/active_record/connection_handling.rb +2 -1
  64. data/lib/active_record/core.rb +5 -4
  65. data/lib/active_record/counter_cache.rb +33 -8
  66. data/lib/active_record/database_configurations/database_config.rb +5 -1
  67. data/lib/active_record/database_configurations/hash_config.rb +53 -9
  68. data/lib/active_record/database_configurations/url_config.rb +13 -3
  69. data/lib/active_record/database_configurations.rb +7 -3
  70. data/lib/active_record/delegated_type.rb +1 -1
  71. data/lib/active_record/dynamic_matchers.rb +54 -69
  72. data/lib/active_record/encryption/encryptable_record.rb +4 -4
  73. data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
  74. data/lib/active_record/encryption/encryptor.rb +12 -0
  75. data/lib/active_record/encryption/scheme.rb +1 -1
  76. data/lib/active_record/enum.rb +24 -8
  77. data/lib/active_record/errors.rb +20 -4
  78. data/lib/active_record/explain.rb +1 -1
  79. data/lib/active_record/explain_registry.rb +51 -2
  80. data/lib/active_record/filter_attribute_handler.rb +73 -0
  81. data/lib/active_record/fixtures.rb +2 -2
  82. data/lib/active_record/gem_version.rb +3 -3
  83. data/lib/active_record/inheritance.rb +1 -1
  84. data/lib/active_record/insert_all.rb +12 -7
  85. data/lib/active_record/locking/optimistic.rb +7 -0
  86. data/lib/active_record/locking/pessimistic.rb +5 -0
  87. data/lib/active_record/log_subscriber.rb +2 -6
  88. data/lib/active_record/middleware/shard_selector.rb +34 -17
  89. data/lib/active_record/migration/command_recorder.rb +14 -1
  90. data/lib/active_record/migration/compatibility.rb +34 -24
  91. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  92. data/lib/active_record/migration.rb +26 -16
  93. data/lib/active_record/model_schema.rb +36 -10
  94. data/lib/active_record/nested_attributes.rb +2 -0
  95. data/lib/active_record/persistence.rb +34 -3
  96. data/lib/active_record/query_cache.rb +22 -15
  97. data/lib/active_record/query_logs.rb +3 -7
  98. data/lib/active_record/railtie.rb +32 -3
  99. data/lib/active_record/railties/controller_runtime.rb +11 -6
  100. data/lib/active_record/railties/databases.rake +15 -3
  101. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  102. data/lib/active_record/railties/job_runtime.rb +10 -11
  103. data/lib/active_record/reflection.rb +42 -3
  104. data/lib/active_record/relation/batches.rb +25 -11
  105. data/lib/active_record/relation/calculations.rb +20 -9
  106. data/lib/active_record/relation/delegation.rb +0 -1
  107. data/lib/active_record/relation/finder_methods.rb +27 -11
  108. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  109. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  110. data/lib/active_record/relation/predicate_builder.rb +9 -7
  111. data/lib/active_record/relation/query_attribute.rb +3 -1
  112. data/lib/active_record/relation/query_methods.rb +38 -28
  113. data/lib/active_record/relation/where_clause.rb +1 -8
  114. data/lib/active_record/relation.rb +24 -12
  115. data/lib/active_record/result.rb +44 -21
  116. data/lib/active_record/runtime_registry.rb +41 -58
  117. data/lib/active_record/sanitization.rb +2 -0
  118. data/lib/active_record/schema_dumper.rb +12 -10
  119. data/lib/active_record/scoping.rb +0 -1
  120. data/lib/active_record/signed_id.rb +43 -15
  121. data/lib/active_record/statement_cache.rb +13 -9
  122. data/lib/active_record/store.rb +44 -19
  123. data/lib/active_record/structured_event_subscriber.rb +85 -0
  124. data/lib/active_record/table_metadata.rb +5 -20
  125. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  126. data/lib/active_record/tasks/database_tasks.rb +25 -34
  127. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  128. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
  129. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  130. data/lib/active_record/test_databases.rb +14 -4
  131. data/lib/active_record/test_fixtures.rb +27 -2
  132. data/lib/active_record/testing/query_assertions.rb +8 -2
  133. data/lib/active_record/timestamp.rb +4 -2
  134. data/lib/active_record/transaction.rb +2 -5
  135. data/lib/active_record/transactions.rb +32 -10
  136. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  137. data/lib/active_record/type/internal/timezone.rb +7 -0
  138. data/lib/active_record/type/json.rb +15 -2
  139. data/lib/active_record/type/serialized.rb +11 -4
  140. data/lib/active_record/type/type_map.rb +1 -1
  141. data/lib/active_record/type_caster/connection.rb +2 -1
  142. data/lib/active_record/validations/associated.rb +1 -1
  143. data/lib/active_record.rb +65 -3
  144. data/lib/arel/alias_predication.rb +2 -0
  145. data/lib/arel/crud.rb +6 -11
  146. data/lib/arel/nodes/count.rb +2 -2
  147. data/lib/arel/nodes/function.rb +4 -10
  148. data/lib/arel/nodes/named_function.rb +2 -2
  149. data/lib/arel/nodes/node.rb +1 -1
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +7 -2
  152. data/lib/arel/visitors/dot.rb +0 -3
  153. data/lib/arel/visitors/postgresql.rb +55 -0
  154. data/lib/arel/visitors/sqlite.rb +55 -8
  155. data/lib/arel/visitors/to_sql.rb +3 -21
  156. data/lib/arel.rb +3 -1
  157. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  158. metadata +14 -10
  159. data/lib/active_record/explain_subscriber.rb +0 -34
  160. data/lib/active_record/normalization.rb +0 -163
@@ -3,80 +3,63 @@
3
3
  module ActiveRecord
4
4
  # This is a thread locals registry for Active Record. For example:
5
5
  #
6
- # ActiveRecord::RuntimeRegistry.sql_runtime
6
+ # ActiveRecord::RuntimeRegistry.stats.sql_runtime
7
7
  #
8
8
  # returns the connection handler local to the current unit of execution (either thread of fiber).
9
9
  module RuntimeRegistry # :nodoc:
10
- extend self
11
-
12
- def sql_runtime
13
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
10
+ class Stats
11
+ attr_accessor :sql_runtime, :async_sql_runtime, :queries_count, :cached_queries_count
12
+
13
+ def initialize
14
+ @sql_runtime = 0.0
15
+ @async_sql_runtime = 0.0
16
+ @queries_count = 0
17
+ @cached_queries_count = 0
18
+ end
19
+
20
+ def reset_runtimes
21
+ sql_runtime_was = @sql_runtime
22
+ @sql_runtime = 0.0
23
+ @async_sql_runtime = 0.0
24
+ sql_runtime_was
25
+ end
26
+
27
+ public alias_method :reset, :initialize
14
28
  end
15
29
 
16
- def sql_runtime=(runtime)
17
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
- end
19
-
20
- def async_sql_runtime
21
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
- end
30
+ extend self
23
31
 
24
- def async_sql_runtime=(runtime)
25
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
32
+ def call(name, start, finish, id, payload)
33
+ record(
34
+ payload[:name],
35
+ (finish - start) * 1_000.0,
36
+ async: payload[:async],
37
+ lock_wait: payload[:lock_wait],
38
+ )
26
39
  end
27
40
 
28
- def queries_count
29
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
- end
41
+ def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
42
+ stats = self.stats
31
43
 
32
- def queries_count=(count)
33
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
- end
44
+ unless query_name == "TRANSACTION" || query_name == "SCHEMA"
45
+ stats.queries_count += 1
46
+ stats.cached_queries_count += 1 if cached
47
+ end
35
48
 
36
- def cached_queries_count
37
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
49
+ if async
50
+ stats.async_sql_runtime += (runtime - lock_wait)
51
+ end
52
+ stats.sql_runtime += runtime
38
53
  end
39
54
 
40
- def cached_queries_count=(count)
41
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
55
+ def stats
56
+ ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
42
57
  end
43
58
 
44
59
  def reset
45
- reset_runtimes
46
- reset_queries_count
47
- reset_cached_queries_count
48
- end
49
-
50
- def reset_runtimes
51
- rt, self.sql_runtime = sql_runtime, 0.0
52
- self.async_sql_runtime = 0.0
53
- rt
54
- end
55
-
56
- def reset_queries_count
57
- qc = queries_count
58
- self.queries_count = 0
59
- qc
60
- end
61
-
62
- def reset_cached_queries_count
63
- qc = cached_queries_count
64
- self.cached_queries_count = 0
65
- qc
60
+ stats.reset
66
61
  end
67
62
  end
68
63
  end
69
64
 
70
- ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
- unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
- ActiveRecord::RuntimeRegistry.queries_count += 1
73
- ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
- end
75
-
76
- runtime = (finish - start) * 1_000.0
77
-
78
- if payload[:async]
79
- ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
80
- end
81
- ActiveRecord::RuntimeRegistry.sql_runtime += runtime
82
- end
65
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
@@ -161,6 +161,8 @@ module ActiveRecord
161
161
  #
162
162
  # sanitize_sql_array(["role = ?", 0])
163
163
  # # => "role = '0'"
164
+ #
165
+ # Before using this method, please consider if Arel.sql would be better for your use-case
164
166
  def sanitize_sql_array(ary)
165
167
  statement, *values = ary
166
168
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
@@ -165,7 +165,7 @@ module ActiveRecord
165
165
  # first dump primary key column
166
166
  pk = @connection.primary_key(table)
167
167
 
168
- tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
168
+ tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
169
169
 
170
170
  case pk
171
171
  when String
@@ -192,7 +192,7 @@ module ActiveRecord
192
192
  tbl.puts ", force: :cascade do |t|"
193
193
 
194
194
  # then dump all non-primary key columns
195
- columns.each do |column|
195
+ columns.sort_by(&:name).each do |column|
196
196
  raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
197
197
  next if column.name == pk
198
198
 
@@ -233,7 +233,7 @@ module ActiveRecord
233
233
  if (indexes = @connection.indexes(table)).any?
234
234
  add_index_statements = indexes.map do |index|
235
235
  table_name = remove_prefix_and_suffix(index.table).inspect
236
- " add_index #{([table_name] + index_parts(index)).join(', ')}"
236
+ " add_index #{([relation_name(table_name)] + index_parts(index)).join(', ')}"
237
237
  end
238
238
 
239
239
  stream.puts add_index_statements.sort.join("\n")
@@ -277,6 +277,7 @@ module ActiveRecord
277
277
  index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
278
278
  index_parts << "type: #{index.type.inspect}" if index.type
279
279
  index_parts << "comment: #{index.comment.inspect}" if index.comment
280
+ index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
280
281
  index_parts
281
282
  end
282
283
 
@@ -317,8 +318,8 @@ module ActiveRecord
317
318
  if (foreign_keys = @connection.foreign_keys(table)).any?
318
319
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
319
320
  parts = [
320
- "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
321
- remove_prefix_and_suffix(foreign_key.to_table).inspect,
321
+ relation_name(remove_prefix_and_suffix(foreign_key.from_table)).inspect,
322
+ relation_name(remove_prefix_and_suffix(foreign_key.to_table)).inspect,
322
323
  ]
323
324
 
324
325
  if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
@@ -329,16 +330,13 @@ module ActiveRecord
329
330
  parts << "primary_key: #{foreign_key.primary_key.inspect}"
330
331
  end
331
332
 
332
- if foreign_key.export_name_on_schema_dump?
333
- parts << "name: #{foreign_key.name.inspect}"
334
- end
335
-
333
+ parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
336
334
  parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
337
335
  parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
338
336
  parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
339
337
  parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
340
338
 
341
- " #{parts.join(', ')}"
339
+ " add_foreign_key #{parts.join(', ')}"
342
340
  end
343
341
 
344
342
  stream.puts add_foreign_key_statements.sort.join("\n")
@@ -363,6 +361,10 @@ module ActiveRecord
363
361
  end
364
362
  end
365
363
 
364
+ def relation_name(name)
365
+ name
366
+ end
367
+
366
368
  def remove_prefix_and_suffix(table)
367
369
  # This method appears at the top when profiling active_record test cases run.
368
370
  # Avoid costly calculation when there are no prefix and suffix.
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  module Scoping
@@ -6,11 +6,27 @@ module ActiveRecord
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
+ class_attribute :_signed_id_verifier, instance_accessor: false, instance_predicate: false
10
+
9
11
  ##
10
12
  # :singleton-method:
11
13
  # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
12
14
  # Within \Rails, this is automatically set using the \Rails application key generator.
13
15
  class_attribute :signed_id_verifier_secret, instance_writer: false
16
+ module DeprecateSignedIdVerifierSecret
17
+ def signed_id_verifier_secret=(secret)
18
+ ActiveRecord.deprecator.warn(<<~MSG)
19
+ ActiveRecord::Base.signed_id_verifier_secret is deprecated and will be removed in Rails 8.2.
20
+
21
+ If the secret is model-specific, set Model.signed_id_verifier instead.
22
+
23
+ Otherwise, configure Rails.application.message_verifiers (or ActiveRecord.message_verifiers) with the secret.
24
+ MSG
25
+
26
+ super
27
+ end
28
+ end
29
+ singleton_class.prepend DeprecateSignedIdVerifierSecret
14
30
  end
15
31
 
16
32
  module RelationMethods # :nodoc:
@@ -49,10 +65,11 @@ module ActiveRecord
49
65
  #
50
66
  # travel_back
51
67
  # User.find_signed signed_id, purpose: :password_reset # => User.first
52
- def find_signed(signed_id, purpose: nil)
68
+ def find_signed(signed_id, purpose: nil, on_rotation: nil)
53
69
  raise UnknownPrimaryKey.new(self) if primary_key.nil?
54
70
 
55
- if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
71
+ options = { on_rotation: on_rotation }.compact
72
+ if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
56
73
  find_by primary_key => id
57
74
  end
58
75
  end
@@ -69,26 +86,33 @@ module ActiveRecord
69
86
  # signed_id = User.first.signed_id
70
87
  # User.first.destroy
71
88
  # User.find_signed! signed_id # => ActiveRecord::RecordNotFound
72
- def find_signed!(signed_id, purpose: nil)
73
- if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose))
89
+ def find_signed!(signed_id, purpose: nil, on_rotation: nil)
90
+ options = { on_rotation: on_rotation }.compact
91
+ if id = signed_id_verifier.verify(signed_id, purpose: combine_signed_id_purposes(purpose), **options)
74
92
  find(id)
75
93
  end
76
94
  end
77
95
 
78
- # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
79
- # with the class-level +signed_id_verifier_secret+, which within Rails comes from
80
- # {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
81
- # By default, it's SHA256 for the digest and JSON for the serialization.
82
96
  def signed_id_verifier
83
- @signed_id_verifier ||= begin
84
- secret = signed_id_verifier_secret
85
- secret = secret.call if secret.respond_to?(:call)
97
+ if signed_id_verifier_secret
98
+ @signed_id_verifier ||= begin
99
+ secret = signed_id_verifier_secret
100
+ secret = secret.call if secret.respond_to?(:call)
101
+
102
+ if secret.nil?
103
+ raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed IDs"
104
+ end
86
105
 
87
- if secret.nil?
88
- raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
89
- else
90
106
  ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
91
107
  end
108
+ else
109
+ return _signed_id_verifier if _signed_id_verifier
110
+
111
+ if ActiveRecord.message_verifiers.nil?
112
+ raise "You must set ActiveRecord.message_verifiers to use signed IDs"
113
+ end
114
+
115
+ ActiveRecord.message_verifiers["active_record/signed_id"]
92
116
  end
93
117
  end
94
118
 
@@ -96,7 +120,11 @@ module ActiveRecord
96
120
  # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
97
121
  # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
98
122
  def signed_id_verifier=(verifier)
99
- @signed_id_verifier = verifier
123
+ if signed_id_verifier_secret
124
+ @signed_id_verifier = verifier
125
+ else
126
+ self._signed_id_verifier = verifier
127
+ end
100
128
  end
101
129
 
102
130
  # :nodoc:
@@ -31,8 +31,11 @@ module ActiveRecord
31
31
  class Substitute; end # :nodoc:
32
32
 
33
33
  class Query # :nodoc:
34
- def initialize(sql)
34
+ attr_reader :retryable
35
+
36
+ def initialize(sql, retryable:)
35
37
  @sql = sql
38
+ @retryable = retryable
36
39
  end
37
40
 
38
41
  def sql_for(binds, connection)
@@ -41,11 +44,12 @@ module ActiveRecord
41
44
  end
42
45
 
43
46
  class PartialQuery < Query # :nodoc:
44
- def initialize(values)
47
+ def initialize(values, retryable:)
45
48
  @values = values
46
49
  @indexes = values.each_with_index.find_all { |thing, i|
47
50
  Substitute === thing
48
51
  }.map(&:last)
52
+ @retryable = retryable
49
53
  end
50
54
 
51
55
  def sql_for(binds, connection)
@@ -94,12 +98,12 @@ module ActiveRecord
94
98
  end
95
99
  end
96
100
 
97
- def self.query(sql)
98
- Query.new(sql)
101
+ def self.query(...)
102
+ Query.new(...)
99
103
  end
100
104
 
101
- def self.partial_query(values)
102
- PartialQuery.new(values)
105
+ def self.partial_query(...)
106
+ PartialQuery.new(...)
103
107
  end
104
108
 
105
109
  def self.partial_query_collector
@@ -142,14 +146,14 @@ module ActiveRecord
142
146
  @model = model
143
147
  end
144
148
 
145
- def execute(params, connection, allow_retry: false, async: false, &block)
149
+ def execute(params, connection, async: false, &block)
146
150
  bind_values = @bind_map.bind params
147
151
  sql = @query_builder.sql_for bind_values, connection
148
152
 
149
153
  if async
150
- @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
154
+ @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
151
155
  else
152
- @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
156
+ @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
153
157
  end
154
158
  rescue ::RangeError
155
159
  async ? Promise.wrap([]) : []
@@ -146,37 +146,43 @@ module ActiveRecord
146
146
  define_method("#{accessor_key}_changed?") do
147
147
  return false unless attribute_changed?(store_attribute)
148
148
  prev_store, new_store = changes[store_attribute]
149
- prev_store&.dig(key) != new_store&.dig(key)
149
+ accessor = store_accessor_for(store_attribute)
150
+ accessor.get(prev_store, key) != accessor.get(new_store, key)
150
151
  end
151
152
 
152
153
  define_method("#{accessor_key}_change") do
153
154
  return unless attribute_changed?(store_attribute)
154
155
  prev_store, new_store = changes[store_attribute]
155
- [prev_store&.dig(key), new_store&.dig(key)]
156
+ accessor = store_accessor_for(store_attribute)
157
+ [accessor.get(prev_store, key), accessor.get(new_store, key)]
156
158
  end
157
159
 
158
160
  define_method("#{accessor_key}_was") do
159
161
  return unless attribute_changed?(store_attribute)
160
162
  prev_store, _new_store = changes[store_attribute]
161
- prev_store&.dig(key)
163
+ accessor = store_accessor_for(store_attribute)
164
+ accessor.get(prev_store, key)
162
165
  end
163
166
 
164
167
  define_method("saved_change_to_#{accessor_key}?") do
165
168
  return false unless saved_change_to_attribute?(store_attribute)
166
169
  prev_store, new_store = saved_changes[store_attribute]
167
- prev_store&.dig(key) != new_store&.dig(key)
170
+ accessor = store_accessor_for(store_attribute)
171
+ accessor.get(prev_store, key) != accessor.get(new_store, key)
168
172
  end
169
173
 
170
174
  define_method("saved_change_to_#{accessor_key}") do
171
175
  return unless saved_change_to_attribute?(store_attribute)
172
176
  prev_store, new_store = saved_changes[store_attribute]
173
- [prev_store&.dig(key), new_store&.dig(key)]
177
+ accessor = store_accessor_for(store_attribute)
178
+ [accessor.get(prev_store, key), accessor.get(new_store, key)]
174
179
  end
175
180
 
176
181
  define_method("#{accessor_key}_before_last_save") do
177
182
  return unless saved_change_to_attribute?(store_attribute)
178
183
  prev_store, _new_store = saved_changes[store_attribute]
179
- prev_store&.dig(key)
184
+ accessor = store_accessor_for(store_attribute)
185
+ accessor.get(prev_store, key)
180
186
  end
181
187
  end
182
188
  end
@@ -225,39 +231,58 @@ module ActiveRecord
225
231
  end
226
232
 
227
233
  class HashAccessor # :nodoc:
234
+ def self.get(store_object, key)
235
+ if store_object
236
+ store_object[key]
237
+ end
238
+ end
239
+
228
240
  def self.read(object, attribute, key)
229
- prepare(object, attribute)
230
- object.public_send(attribute)[key]
241
+ store_object = prepare(object, attribute)
242
+ store_object[key]
231
243
  end
232
244
 
233
245
  def self.write(object, attribute, key, value)
234
- prepare(object, attribute)
235
- object.public_send(attribute)[key] = value if value != read(object, attribute, key)
246
+ store_object = prepare(object, attribute)
247
+ store_object[key] = value if value != store_object[key]
236
248
  end
237
249
 
238
250
  def self.prepare(object, attribute)
239
- object.public_send :"#{attribute}=", {} unless object.send(attribute)
251
+ store_object = object.public_send(attribute)
252
+
253
+ if store_object.nil?
254
+ store_object = {}
255
+ object.public_send(:"#{attribute}=", store_object)
256
+ end
257
+
258
+ store_object
240
259
  end
241
260
  end
242
261
 
243
262
  class StringKeyedHashAccessor < HashAccessor # :nodoc:
263
+ def self.get(store_object, key)
264
+ super store_object, Symbol === key ? key.name : key.to_s
265
+ end
266
+
244
267
  def self.read(object, attribute, key)
245
- super object, attribute, key.to_s
268
+ super object, attribute, Symbol === key ? key.name : key.to_s
246
269
  end
247
270
 
248
271
  def self.write(object, attribute, key, value)
249
- super object, attribute, key.to_s, value
272
+ super object, attribute, Symbol === key ? key.name : key.to_s, value
250
273
  end
251
274
  end
252
275
 
253
276
  class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
254
- def self.prepare(object, store_attribute)
255
- attribute = object.send(store_attribute)
256
- unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
257
- attribute = IndifferentCoder.as_indifferent_hash(attribute)
258
- object.public_send :"#{store_attribute}=", attribute
277
+ def self.prepare(object, attribute)
278
+ store_object = object.public_send(attribute)
279
+
280
+ unless store_object.is_a?(ActiveSupport::HashWithIndifferentAccess)
281
+ store_object = IndifferentCoder.as_indifferent_hash(store_object)
282
+ object.public_send :"#{attribute}=", store_object
259
283
  end
260
- attribute
284
+
285
+ store_object
261
286
  end
262
287
  end
263
288
 
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/structured_event_subscriber"
4
+
5
+ module ActiveRecord
6
+ class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
7
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
8
+
9
+ def strict_loading_violation(event)
10
+ owner = event.payload[:owner]
11
+ reflection = event.payload[:reflection]
12
+
13
+ emit_debug_event("active_record.strict_loading_violation",
14
+ owner: owner.name,
15
+ class: reflection.klass.name,
16
+ name: reflection.name,
17
+ )
18
+ end
19
+ debug_only :strict_loading_violation
20
+
21
+ def sql(event)
22
+ payload = event.payload
23
+
24
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
25
+
26
+ binds = nil
27
+
28
+ if payload[:binds]&.any?
29
+ casted_params = type_casted_binds(payload[:type_casted_binds])
30
+
31
+ binds = []
32
+ payload[:binds].each_with_index do |attr, i|
33
+ attribute_name = if attr.respond_to?(:name)
34
+ attr.name
35
+ elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
36
+ attr[i].name
37
+ else
38
+ nil
39
+ end
40
+
41
+ filtered_params = filter(attribute_name, casted_params[i])
42
+
43
+ binds << render_bind(attr, filtered_params)
44
+ end
45
+ end
46
+
47
+ emit_debug_event("active_record.sql",
48
+ async: payload[:async],
49
+ name: payload[:name],
50
+ sql: payload[:sql],
51
+ cached: payload[:cached],
52
+ lock_wait: payload[:lock_wait],
53
+ binds: binds,
54
+ duration_ms: event.duration.round(2),
55
+ )
56
+ end
57
+ debug_only :sql
58
+
59
+ private
60
+ def type_casted_binds(casted_binds)
61
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
62
+ end
63
+
64
+ def render_bind(attr, value)
65
+ case attr
66
+ when ActiveModel::Attribute
67
+ if attr.type.binary? && attr.value
68
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
69
+ end
70
+ when Array
71
+ attr = attr.first
72
+ else
73
+ attr = nil
74
+ end
75
+
76
+ [attr&.name, value]
77
+ end
78
+
79
+ def filter(name, value)
80
+ ActiveRecord::Base.inspection_filter.filter_param(name, value)
81
+ end
82
+ end
83
+ end
84
+
85
+ ActiveRecord::StructuredEventSubscriber.attach_to :active_record
@@ -2,12 +2,9 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
- delegate :join_primary_key, :join_primary_type, :join_foreign_key, :join_foreign_type, to: :reflection
6
-
7
- def initialize(klass, arel_table, reflection = nil)
5
+ def initialize(klass, arel_table)
8
6
  @klass = klass
9
7
  @arel_table = arel_table
10
- @reflection = reflection
11
8
  end
12
9
 
13
10
  def primary_key
@@ -22,7 +19,7 @@ module ActiveRecord
22
19
  klass&.columns_hash&.key?(column_name)
23
20
  end
24
21
 
25
- def associated_with?(table_name)
22
+ def associated_with(table_name)
26
23
  klass&._reflect_on_association(table_name)
27
24
  end
28
25
 
@@ -42,26 +39,14 @@ module ActiveRecord
42
39
  if association_klass
43
40
  arel_table = association_klass.arel_table
44
41
  arel_table = arel_table.alias(table_name) if arel_table.name != table_name
45
- TableMetadata.new(association_klass, arel_table, reflection)
42
+ TableMetadata.new(association_klass, arel_table)
46
43
  else
47
44
  type_caster = TypeCaster::Connection.new(klass, table_name)
48
45
  arel_table = Arel::Table.new(table_name, type_caster: type_caster)
49
- TableMetadata.new(nil, arel_table, reflection)
46
+ TableMetadata.new(nil, arel_table)
50
47
  end
51
48
  end
52
49
 
53
- def polymorphic_association?
54
- reflection&.polymorphic?
55
- end
56
-
57
- def polymorphic_name_association
58
- reflection&.polymorphic_name
59
- end
60
-
61
- def through_association?
62
- reflection&.through_reflection?
63
- end
64
-
65
50
  def reflect_on_aggregation(aggregation_name)
66
51
  klass&.reflect_on_aggregation(aggregation_name)
67
52
  end
@@ -78,6 +63,6 @@ module ActiveRecord
78
63
  attr_reader :arel_table
79
64
 
80
65
  private
81
- attr_reader :klass, :reflection
66
+ attr_reader :klass
82
67
  end
83
68
  end