activerecord 7.1.5.2 → 8.1.2.1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -41,8 +41,10 @@ module ActiveRecord
41
41
  cattr_accessor :unique_ignore_pattern, default: /^uniq_rails_[0-9a-f]{10}$/
42
42
 
43
43
  class << self
44
- def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base)
45
- connection.create_schema_dumper(generate_options(config)).dump(stream)
44
+ def dump(pool = ActiveRecord::Base.connection_pool, stream = $stdout, config = ActiveRecord::Base)
45
+ pool.with_connection do |connection|
46
+ connection.create_schema_dumper(generate_options(config)).dump(stream)
47
+ end
46
48
  stream
47
49
  end
48
50
 
@@ -61,6 +63,7 @@ module ActiveRecord
61
63
  extensions(stream)
62
64
  types(stream)
63
65
  tables(stream)
66
+ virtual_tables(stream)
64
67
  trailer(stream)
65
68
  stream
66
69
  end
@@ -70,7 +73,7 @@ module ActiveRecord
70
73
 
71
74
  def initialize(connection, options = {})
72
75
  @connection = connection
73
- @version = connection.migration_context.current_version rescue nil
76
+ @version = connection.pool.migration_context.current_version rescue nil
74
77
  @options = options
75
78
  @ignore_tables = [
76
79
  ActiveRecord::Base.schema_migrations_table_name,
@@ -124,18 +127,31 @@ module ActiveRecord
124
127
  def schemas(stream)
125
128
  end
126
129
 
130
+ # virtual tables are only supported by SQLite
131
+ def virtual_tables(stream)
132
+ end
133
+
127
134
  def tables(stream)
128
135
  sorted_tables = @connection.tables.sort
129
136
 
130
- sorted_tables.each do |table_name|
131
- table(table_name, stream) unless ignored?(table_name)
137
+ not_ignored_tables = sorted_tables.reject { |table_name| ignored?(table_name) }
138
+
139
+ not_ignored_tables.each_with_index do |table_name, index|
140
+ table(table_name, stream)
141
+ stream.puts if index < not_ignored_tables.count - 1
132
142
  end
133
143
 
134
144
  # dump foreign keys at the end to make sure all dependent tables exist.
135
- if @connection.use_foreign_keys?
136
- sorted_tables.each do |tbl|
137
- foreign_keys(tbl, stream) unless ignored?(tbl)
145
+ if @connection.supports_foreign_keys?
146
+ foreign_keys_stream = StringIO.new
147
+ not_ignored_tables.each do |tbl|
148
+ foreign_keys(tbl, foreign_keys_stream)
138
149
  end
150
+
151
+ foreign_keys_string = foreign_keys_stream.string
152
+ stream.puts if foreign_keys_string.length > 0
153
+
154
+ stream.print foreign_keys_string
139
155
  end
140
156
  end
141
157
 
@@ -149,7 +165,7 @@ module ActiveRecord
149
165
  # first dump primary key column
150
166
  pk = @connection.primary_key(table)
151
167
 
152
- tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
168
+ tbl.print " create_table #{relation_name(remove_prefix_and_suffix(table)).inspect}"
153
169
 
154
170
  case pk
155
171
  when String
@@ -176,7 +192,7 @@ module ActiveRecord
176
192
  tbl.puts ", force: :cascade do |t|"
177
193
 
178
194
  # then dump all non-primary key columns
179
- columns.each do |column|
195
+ columns.sort_by(&:name).each do |column|
180
196
  raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
181
197
  next if column.name == pk
182
198
 
@@ -191,12 +207,16 @@ module ActiveRecord
191
207
  end
192
208
 
193
209
  indexes_in_create(table, tbl)
194
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
210
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
195
211
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
196
212
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
197
213
 
198
214
  tbl.puts " end"
199
- tbl.puts
215
+
216
+ if remaining
217
+ tbl.puts
218
+ tbl.print remaining.string
219
+ end
200
220
 
201
221
  stream.print tbl.string
202
222
  rescue => e
@@ -212,8 +232,8 @@ module ActiveRecord
212
232
  def indexes(table, stream)
213
233
  if (indexes = @connection.indexes(table)).any?
214
234
  add_index_statements = indexes.map do |index|
215
- table_name = remove_prefix_and_suffix(index.table).inspect
216
- " add_index #{([table_name] + index_parts(index)).join(', ')}"
235
+ table_name = remove_prefix_and_suffix(index.table)
236
+ " add_index #{([relation_name(table_name).inspect] + index_parts(index)).join(', ')}"
217
237
  end
218
238
 
219
239
  stream.puts add_index_statements.sort.join("\n")
@@ -257,35 +277,49 @@ module ActiveRecord
257
277
  index_parts << "nulls_not_distinct: #{index.nulls_not_distinct.inspect}" if index.nulls_not_distinct
258
278
  index_parts << "type: #{index.type.inspect}" if index.type
259
279
  index_parts << "comment: #{index.comment.inspect}" if index.comment
280
+ index_parts << "enabled: #{index.enabled.inspect}" if @connection.supports_disabling_indexes? && index.disabled?
260
281
  index_parts
261
282
  end
262
283
 
263
284
  def check_constraints_in_create(table, stream)
264
285
  if (check_constraints = @connection.check_constraints(table)).any?
265
- add_check_constraint_statements = check_constraints.map do |check_constraint|
266
- parts = [
267
- "t.check_constraint #{check_constraint.expression.inspect}"
268
- ]
286
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
269
287
 
270
- if check_constraint.export_name_on_schema_dump?
271
- parts << "name: #{check_constraint.name.inspect}"
288
+ unless check_valid.empty?
289
+ check_constraint_statements = check_valid.map do |check|
290
+ " t.check_constraint #{check_parts(check).join(', ')}"
272
291
  end
273
292
 
274
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
275
-
276
- " #{parts.join(', ')}"
293
+ stream.puts check_constraint_statements.sort.join("\n")
277
294
  end
278
295
 
279
- stream.puts add_check_constraint_statements.sort.join("\n")
296
+ unless check_invalid.empty?
297
+ remaining = StringIO.new
298
+ table_name = remove_prefix_and_suffix(table).inspect
299
+
300
+ add_check_constraint_statements = check_invalid.map do |check|
301
+ " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
302
+ end
303
+
304
+ remaining.puts add_check_constraint_statements.sort.join("\n")
305
+ remaining
306
+ end
280
307
  end
281
308
  end
282
309
 
310
+ def check_parts(check)
311
+ check_parts = [ check.expression.inspect ]
312
+ check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
313
+ check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
314
+ check_parts
315
+ end
316
+
283
317
  def foreign_keys(table, stream)
284
318
  if (foreign_keys = @connection.foreign_keys(table)).any?
285
319
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
286
320
  parts = [
287
- "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
288
- 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,
289
323
  ]
290
324
 
291
325
  if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table, "id")
@@ -296,16 +330,13 @@ module ActiveRecord
296
330
  parts << "primary_key: #{foreign_key.primary_key.inspect}"
297
331
  end
298
332
 
299
- if foreign_key.export_name_on_schema_dump?
300
- parts << "name: #{foreign_key.name.inspect}"
301
- end
302
-
333
+ parts << "name: #{foreign_key.name.inspect}" if foreign_key.export_name_on_schema_dump?
303
334
  parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
304
335
  parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
305
336
  parts << "deferrable: #{foreign_key.deferrable.inspect}" if foreign_key.deferrable
306
337
  parts << "validate: #{foreign_key.validate?.inspect}" unless foreign_key.validate?
307
338
 
308
- " #{parts.join(', ')}"
339
+ " add_foreign_key #{parts.join(', ')}"
309
340
  end
310
341
 
311
342
  stream.puts add_foreign_key_statements.sort.join("\n")
@@ -330,6 +361,10 @@ module ActiveRecord
330
361
  end
331
362
  end
332
363
 
364
+ def relation_name(name)
365
+ name
366
+ end
367
+
333
368
  def remove_prefix_and_suffix(table)
334
369
  # This method appears at the top when profiling active_record test cases run.
335
370
  # Avoid costly calculation when there are no prefix and suffix.
@@ -9,29 +9,36 @@ module ActiveRecord
9
9
  class NullSchemaMigration # :nodoc:
10
10
  end
11
11
 
12
- attr_reader :connection, :arel_table
12
+ attr_reader :arel_table
13
13
 
14
- def initialize(connection)
15
- @connection = connection
14
+ def initialize(pool)
15
+ @pool = pool
16
16
  @arel_table = Arel::Table.new(table_name)
17
17
  end
18
18
 
19
19
  def create_version(version)
20
20
  im = Arel::InsertManager.new(arel_table)
21
21
  im.insert(arel_table[primary_key] => version)
22
- connection.insert(im, "#{self.class} Create", primary_key, version)
22
+ @pool.with_connection do |connection|
23
+ connection.insert(im, "#{self.class} Create", primary_key, version)
24
+ end
23
25
  end
24
26
 
25
27
  def delete_version(version)
26
28
  dm = Arel::DeleteManager.new(arel_table)
27
29
  dm.wheres = [arel_table[primary_key].eq(version)]
28
30
 
29
- connection.delete(dm, "#{self.class} Destroy")
31
+ @pool.with_connection do |connection|
32
+ connection.delete(dm, "#{self.class} Destroy")
33
+ end
30
34
  end
31
35
 
32
36
  def delete_all_versions
33
- versions.each do |version|
34
- delete_version(version)
37
+ # Eagerly check in connection to avoid checking in/out many times in the called method.
38
+ @pool.with_connection do
39
+ versions.each do |version|
40
+ delete_version(version)
41
+ end
35
42
  end
36
43
  end
37
44
 
@@ -44,15 +51,19 @@ module ActiveRecord
44
51
  end
45
52
 
46
53
  def create_table
47
- unless connection.table_exists?(table_name)
48
- connection.create_table(table_name, id: false) do |t|
49
- t.string :version, **connection.internal_string_options_for_primary_key
54
+ @pool.with_connection do |connection|
55
+ unless connection.table_exists?(table_name)
56
+ connection.create_table(table_name, id: false) do |t|
57
+ t.string :version, **connection.internal_string_options_for_primary_key
58
+ end
50
59
  end
51
60
  end
52
61
  end
53
62
 
54
63
  def drop_table
55
- connection.drop_table table_name, if_exists: true
64
+ @pool.with_connection do |connection|
65
+ connection.drop_table table_name, if_exists: true
66
+ end
56
67
  end
57
68
 
58
69
  def normalize_migration_number(number)
@@ -68,7 +79,9 @@ module ActiveRecord
68
79
  sm.project(arel_table[primary_key])
69
80
  sm.order(arel_table[primary_key].asc)
70
81
 
71
- connection.select_values(sm, "#{self.class} Load")
82
+ @pool.with_connection do |connection|
83
+ connection.select_values(sm, "#{self.class} Load")
84
+ end
72
85
  end
73
86
 
74
87
  def integer_versions
@@ -79,11 +92,15 @@ module ActiveRecord
79
92
  sm = Arel::SelectManager.new(arel_table)
80
93
  sm.project(*Arel::Nodes::Count.new([Arel.star]))
81
94
 
82
- connection.select_values(sm, "#{self.class} Count").first
95
+ @pool.with_connection do |connection|
96
+ connection.select_values(sm, "#{self.class} Count").first
97
+ end
83
98
  end
84
99
 
85
100
  def table_exists?
86
- connection.data_source_exists?(table_name)
101
+ @pool.with_connection do |connection|
102
+ connection.data_source_exists?(table_name)
103
+ end
87
104
  end
88
105
  end
89
106
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  scope = current_scope
24
24
 
25
25
  if scope
26
- if self == scope.klass
26
+ if self == scope.model
27
27
  scope.clone
28
28
  else
29
29
  relation.merge!(scope)
@@ -190,7 +190,11 @@ module ActiveRecord
190
190
 
191
191
  private
192
192
  def singleton_method_added(name)
193
- generate_relation_method(name) if Kernel.respond_to?(name) && !ActiveRecord::Relation.method_defined?(name)
193
+ super
194
+ # Most Kernel extends are both singleton and instance methods so
195
+ # respond_to is a fast check, but we don't want to define methods
196
+ # only on the module (ex. Module#name)
197
+ generate_relation_method(name) if Kernel.respond_to?(name) && (Kernel.method_defined?(name) || Kernel.private_method_defined?(name)) && !ActiveRecord::Relation.method_defined?(name)
194
198
  end
195
199
  end
196
200
  end
@@ -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
@@ -30,13 +30,13 @@ module ActiveRecord
30
30
  # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
31
31
  # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
32
32
  #
33
- # === Options
33
+ # ==== Options
34
34
  #
35
- # [:length]
35
+ # [+:length+]
36
36
  # Length of the Secure Random, with a minimum of 24 characters. It will
37
37
  # default to 24.
38
38
  #
39
- # [:on]
39
+ # [+:on+]
40
40
  # The callback when the value is generated. When called with <tt>on:
41
41
  # :initialize</tt>, the value is generated in an
42
42
  # <tt>after_initialize</tt> callback, otherwise the value will be used
@@ -6,11 +6,37 @@ 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
30
+ end
31
+
32
+ module RelationMethods # :nodoc:
33
+ def find_signed(...)
34
+ scoping { model.find_signed(...) }
35
+ end
36
+
37
+ def find_signed!(...)
38
+ scoping { model.find_signed!(...) }
39
+ end
14
40
  end
15
41
 
16
42
  module ClassMethods
@@ -39,53 +65,66 @@ module ActiveRecord
39
65
  #
40
66
  # travel_back
41
67
  # User.find_signed signed_id, purpose: :password_reset # => User.first
42
- def find_signed(signed_id, purpose: nil)
68
+ def find_signed(signed_id, purpose: nil, on_rotation: nil)
43
69
  raise UnknownPrimaryKey.new(self) if primary_key.nil?
44
70
 
45
- 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)
46
73
  find_by primary_key => id
47
74
  end
48
75
  end
49
76
 
50
- # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
77
+ # Works like find_signed, but will raise an ActiveSupport::MessageVerifier::InvalidSignature
51
78
  # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
52
- # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
79
+ # or has been tampered with. It will also raise an ActiveRecord::RecordNotFound exception if
53
80
  # the valid signed id can't find a record.
54
81
  #
55
- # === Examples
82
+ # ==== Examples
56
83
  #
57
84
  # User.find_signed! "bad data" # => ActiveSupport::MessageVerifier::InvalidSignature
58
85
  #
59
86
  # signed_id = User.first.signed_id
60
87
  # User.first.destroy
61
88
  # User.find_signed! signed_id # => ActiveRecord::RecordNotFound
62
- def find_signed!(signed_id, purpose: nil)
63
- 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)
64
92
  find(id)
65
93
  end
66
94
  end
67
95
 
68
- # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
69
- # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
70
- # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
71
96
  def signed_id_verifier
72
- @signed_id_verifier ||= begin
73
- secret = signed_id_verifier_secret
74
- secret = secret.call if secret.respond_to?(:call)
75
-
76
- if secret.nil?
77
- raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
78
- else
79
- ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON
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
105
+
106
+ ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
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"
80
113
  end
114
+
115
+ ActiveRecord.message_verifiers["active_record/signed_id"]
81
116
  end
82
117
  end
83
118
 
84
119
  # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
85
120
  # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
86
- # your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
121
+ # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
87
122
  def signed_id_verifier=(verifier)
88
- @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
89
128
  end
90
129
 
91
130
  # :nodoc:
@@ -96,7 +135,16 @@ module ActiveRecord
96
135
 
97
136
 
98
137
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
138
+ #
99
139
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
140
+ # However, as with any message signed with a +ActiveSupport::MessageVerifier+,
141
+ # {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
142
+ # It's just encoded and protected against tampering.
143
+ #
144
+ # This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
145
+ # the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
146
+ # when passed to +find_signed+ (or raise with +find_signed!+).
147
+ #
100
148
  # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
101
149
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
102
150
  # record. If a purpose is set, this too must match.
@@ -4,14 +4,14 @@ module ActiveRecord
4
4
  # Statement cache is used to cache a single statement in order to avoid creating the AST again.
5
5
  # Initializing the cache is done by passing the statement in the create block:
6
6
  #
7
- # cache = StatementCache.create(Book.connection) do |params|
7
+ # cache = StatementCache.create(ClothingItem.lease_connection) do |params|
8
8
  # Book.where(name: "my book").where("author_id > 3")
9
9
  # end
10
10
  #
11
11
  # The cached statement is executed by using the
12
12
  # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
13
13
  #
14
- # cache.execute([], Book.connection)
14
+ # cache.execute([], ClothingItem.lease_connection)
15
15
  #
16
16
  # The relation returned by the block is cached, and for each
17
17
  # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
@@ -20,19 +20,22 @@ module ActiveRecord
20
20
  # If you want to cache the statement without the values you can use the +bind+ method of the
21
21
  # block parameter.
22
22
  #
23
- # cache = StatementCache.create(Book.connection) do |params|
23
+ # cache = StatementCache.create(ClothingItem.lease_connection) do |params|
24
24
  # Book.where(name: params.bind)
25
25
  # end
26
26
  #
27
27
  # And pass the bind values as the first argument of +execute+ call.
28
28
  #
29
- # cache.execute(["my book"], Book.connection)
29
+ # cache.execute(["my book"], ClothingItem.lease_connection)
30
30
  class StatementCache # :nodoc:
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)
@@ -62,7 +66,7 @@ module ActiveRecord
62
66
  end
63
67
 
64
68
  class PartialQueryCollector
65
- attr_accessor :preparable
69
+ attr_accessor :preparable, :retryable
66
70
 
67
71
  def initialize
68
72
  @parts = []
@@ -74,13 +78,13 @@ module ActiveRecord
74
78
  self
75
79
  end
76
80
 
77
- def add_bind(obj)
81
+ def add_bind(obj, &)
78
82
  @binds << obj
79
83
  @parts << Substitute.new
80
84
  self
81
85
  end
82
86
 
83
- def add_binds(binds, proc_for_binds = nil)
87
+ def add_binds(binds, proc_for_binds = nil, &)
84
88
  @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds
85
89
  binds.size.times do |i|
86
90
  @parts << ", " unless i == 0
@@ -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
@@ -133,23 +137,26 @@ module ActiveRecord
133
137
  relation = (callable || block).call Params.new
134
138
  query_builder, binds = connection.cacheable_query(self, relation.arel)
135
139
  bind_map = BindMap.new(binds)
136
- new(query_builder, bind_map, relation.klass)
140
+ new(query_builder, bind_map, relation.model)
137
141
  end
138
142
 
139
- def initialize(query_builder, bind_map, klass)
143
+ def initialize(query_builder, bind_map, model)
140
144
  @query_builder = query_builder
141
145
  @bind_map = bind_map
142
- @klass = klass
146
+ @model = model
143
147
  end
144
148
 
145
- def execute(params, connection, &block)
146
- bind_values = bind_map.bind params
147
-
148
- sql = query_builder.sql_for bind_values, connection
149
+ def execute(params, connection, async: false, &block)
150
+ bind_values = @bind_map.bind params
151
+ sql = @query_builder.sql_for bind_values, connection
149
152
 
150
- klass.find_by_sql(sql, bind_values, preparable: true, &block)
153
+ if async
154
+ @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
155
+ else
156
+ @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
157
+ end
151
158
  rescue ::RangeError
152
- []
159
+ async ? Promise.wrap([]) : []
153
160
  end
154
161
 
155
162
  def self.unsupported_value?(value)
@@ -157,8 +164,5 @@ module ActiveRecord
157
164
  when NilClass, Array, Range, Hash, Relation, Base then true
158
165
  end
159
166
  end
160
-
161
- private
162
- attr_reader :query_builder, :bind_map, :klass
163
167
  end
164
168
  end