activerecord 7.0.8.7 → 7.2.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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -6,6 +6,13 @@ module ActiveRecord
6
6
  module ModelSchema
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ ##
10
+ # :method: id_value
11
+ # :call-seq: id_value
12
+ #
13
+ # Returns the underlying column value for a column named "id". Useful when defining
14
+ # a composite primary key including an "id" column so that the value is readable.
15
+
9
16
  ##
10
17
  # :singleton-method: primary_key_prefix_type
11
18
  # :call-seq: primary_key_prefix_type
@@ -139,6 +146,11 @@ module ActiveRecord
139
146
  # your own model for something else, you can set +inheritance_column+:
140
147
  #
141
148
  # self.inheritance_column = 'zoink'
149
+ #
150
+ # If you wish to disable single-table inheritance altogether you can set
151
+ # +inheritance_column+ to +nil+
152
+ #
153
+ # self.inheritance_column = nil
142
154
 
143
155
  ##
144
156
  # :singleton-method: inheritance_column=
@@ -180,8 +192,9 @@ module ActiveRecord
180
192
  # artists, records => artists_records
181
193
  # records, artists => artists_records
182
194
  # music_artists, music_records => music_artists_records
195
+ # music.artists, music.records => music.artists_records
183
196
  def self.derive_join_table_name(first_table, second_table) # :nodoc:
184
- [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
197
+ [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*[_.])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
185
198
  end
186
199
 
187
200
  module ClassMethods
@@ -265,19 +278,21 @@ module ActiveRecord
265
278
  @table_name = value
266
279
  @quoted_table_name = nil
267
280
  @arel_table = nil
268
- @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
281
+ @sequence_name = nil unless @explicit_sequence_name
269
282
  @predicate_builder = nil
270
283
  end
271
284
 
272
285
  # Returns a quoted version of the table name, used to construct SQL statements.
273
286
  def quoted_table_name
274
- @quoted_table_name ||= connection.quote_table_name(table_name)
287
+ @quoted_table_name ||= adapter_class.quote_table_name(table_name)
275
288
  end
276
289
 
277
290
  # Computes the table name, (re)sets it internally, and returns it.
278
291
  def reset_table_name # :nodoc:
279
- self.table_name = if abstract_class?
280
- superclass == Base ? nil : superclass.table_name
292
+ self.table_name = if self == Base
293
+ nil
294
+ elsif abstract_class?
295
+ superclass.table_name
281
296
  elsif superclass.abstract_class?
282
297
  superclass.table_name || compute_table_name
283
298
  else
@@ -315,11 +330,7 @@ module ActiveRecord
315
330
  # The list of columns names the model should ignore. Ignored columns won't have attribute
316
331
  # accessors defined, and won't be referenced in SQL queries.
317
332
  def ignored_columns
318
- if defined?(@ignored_columns)
319
- @ignored_columns
320
- else
321
- superclass.ignored_columns
322
- end
333
+ @ignored_columns || superclass.ignored_columns
323
334
  end
324
335
 
325
336
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
@@ -340,7 +351,7 @@ module ActiveRecord
340
351
  # # name :string, limit: 255
341
352
  # # category :string, limit: 255
342
353
  #
343
- # self.ignored_columns = [:category]
354
+ # self.ignored_columns += [:category]
344
355
  # end
345
356
  #
346
357
  # The schema still contains "category", but now the model omits it, so any meta-driven code or
@@ -368,7 +379,7 @@ module ActiveRecord
368
379
 
369
380
  def reset_sequence_name # :nodoc:
370
381
  @explicit_sequence_name = false
371
- @sequence_name = connection.default_sequence_name(table_name, primary_key)
382
+ @sequence_name = with_connection { |c| c.default_sequence_name(table_name, primary_key) }
372
383
  end
373
384
 
374
385
  # Sets the name of the sequence to use when generating ids to the given
@@ -393,71 +404,52 @@ module ActiveRecord
393
404
  # Determines if the primary key values should be selected from their
394
405
  # corresponding sequence before the insert statement.
395
406
  def prefetch_primary_key?
396
- connection.prefetch_primary_key?(table_name)
407
+ with_connection { |c| c.prefetch_primary_key?(table_name) }
397
408
  end
398
409
 
399
410
  # Returns the next value that will be used as the primary key on
400
411
  # an insert statement.
401
412
  def next_sequence_value
402
- connection.next_sequence_value(sequence_name)
413
+ with_connection { |c| c.next_sequence_value(sequence_name) }
403
414
  end
404
415
 
405
416
  # Indicates whether the table associated with this class exists
406
417
  def table_exists?
407
- connection.schema_cache.data_source_exists?(table_name)
418
+ schema_cache.data_source_exists?(table_name)
408
419
  end
409
420
 
410
421
  def attributes_builder # :nodoc:
411
- unless defined?(@attributes_builder) && @attributes_builder
422
+ @attributes_builder ||= begin
412
423
  defaults = _default_attributes.except(*(column_names - [primary_key]))
413
- @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
424
+ ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
414
425
  end
415
- @attributes_builder
416
426
  end
417
427
 
418
428
  def columns_hash # :nodoc:
419
- load_schema
429
+ load_schema unless @columns_hash
420
430
  @columns_hash
421
431
  end
422
432
 
423
433
  def columns
424
- load_schema
425
434
  @columns ||= columns_hash.values.freeze
426
435
  end
427
436
 
428
- def attribute_types # :nodoc:
429
- load_schema
430
- @attribute_types ||= Hash.new(Type.default_value)
437
+ def _returning_columns_for_insert(connection) # :nodoc:
438
+ @_returning_columns_for_insert ||= begin
439
+ auto_populated_columns = columns.filter_map do |c|
440
+ c.name if connection.return_value_after_insert?(c)
441
+ end
442
+
443
+ auto_populated_columns.empty? ? Array(primary_key) : auto_populated_columns
444
+ end
431
445
  end
432
446
 
433
447
  def yaml_encoder # :nodoc:
434
448
  @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
435
449
  end
436
450
 
437
- # Returns the type of the attribute with the given name, after applying
438
- # all modifiers. This method is the only valid source of information for
439
- # anything related to the types of a model's attributes. This method will
440
- # access the database and load the model's schema if it is required.
441
- #
442
- # The return value of this method will implement the interface described
443
- # by ActiveModel::Type::Value (though the object itself may not subclass
444
- # it).
445
- #
446
- # +attr_name+ The name of the attribute to retrieve the type for. Must be
447
- # a string or a symbol.
448
- def type_for_attribute(attr_name, &block)
449
- attr_name = attr_name.to_s
450
- attr_name = attribute_aliases[attr_name] || attr_name
451
-
452
- if block
453
- attribute_types.fetch(attr_name, &block)
454
- else
455
- attribute_types[attr_name]
456
- end
457
- end
458
-
459
451
  # Returns the column object for the named attribute.
460
- # Returns an +ActiveRecord::ConnectionAdapters::NullColumn+ if the
452
+ # Returns an ActiveRecord::ConnectionAdapters::NullColumn if the
461
453
  # named attribute does not exist.
462
454
  #
463
455
  # class Person < ActiveRecord::Base
@@ -483,11 +475,6 @@ module ActiveRecord
483
475
  @column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
484
476
  end
485
477
 
486
- def _default_attributes # :nodoc:
487
- load_schema
488
- @default_attributes ||= ActiveModel::AttributeSet.new({})
489
- end
490
-
491
478
  # Returns an array of column names as strings.
492
479
  def column_names
493
480
  @column_names ||= columns.map(&:name).freeze
@@ -515,7 +502,7 @@ module ActiveRecord
515
502
  # when just after creating a table you want to populate it with some default
516
503
  # values, e.g.:
517
504
  #
518
- # class CreateJobLevels < ActiveRecord::Migration[7.0]
505
+ # class CreateJobLevels < ActiveRecord::Migration[7.2]
519
506
  # def up
520
507
  # create_table :job_levels do |t|
521
508
  # t.integer :id
@@ -535,41 +522,67 @@ module ActiveRecord
535
522
  # end
536
523
  # end
537
524
  def reset_column_information
538
- connection.clear_cache!
525
+ connection_pool.active_connection&.clear_cache!
539
526
  ([self] + descendants).each(&:undefine_attribute_methods)
540
- connection.schema_cache.clear_data_source_cache!(table_name)
527
+ schema_cache.clear_data_source_cache!(table_name)
541
528
 
542
529
  reload_schema_from_cache
543
530
  initialize_find_by_cache
544
531
  end
545
532
 
533
+ # Load the model's schema information either from the schema cache
534
+ # or directly from the database.
535
+ def load_schema
536
+ return if schema_loaded?
537
+ @load_schema_monitor.synchronize do
538
+ return if schema_loaded?
539
+
540
+ load_schema!
541
+
542
+ @schema_loaded = true
543
+ rescue
544
+ reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
545
+ raise
546
+ end
547
+ end
548
+
546
549
  protected
547
550
  def initialize_load_schema_monitor
548
551
  @load_schema_monitor = Monitor.new
549
552
  end
550
553
 
554
+ def reload_schema_from_cache(recursive = true)
555
+ @_returning_columns_for_insert = nil
556
+ @arel_table = nil
557
+ @column_names = nil
558
+ @symbol_column_to_string_name_hash = nil
559
+ @content_columns = nil
560
+ @column_defaults = nil
561
+ @attributes_builder = nil
562
+ @columns = nil
563
+ @columns_hash = nil
564
+ @schema_loaded = false
565
+ @attribute_names = nil
566
+ @yaml_encoder = nil
567
+ if recursive
568
+ subclasses.each do |descendant|
569
+ descendant.send(:reload_schema_from_cache)
570
+ end
571
+ end
572
+ end
573
+
551
574
  private
552
575
  def inherited(child_class)
553
576
  super
554
577
  child_class.initialize_load_schema_monitor
578
+ child_class.reload_schema_from_cache(false)
579
+ child_class.class_eval do
580
+ @ignored_columns = nil
581
+ end
555
582
  end
556
583
 
557
584
  def schema_loaded?
558
- defined?(@schema_loaded) && @schema_loaded
559
- end
560
-
561
- def load_schema
562
- return if schema_loaded?
563
- @load_schema_monitor.synchronize do
564
- return if defined?(@columns_hash) && @columns_hash
565
-
566
- load_schema!
567
-
568
- @schema_loaded = true
569
- rescue
570
- reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
571
- raise
572
- end
585
+ @schema_loaded
573
586
  end
574
587
 
575
588
  def load_schema!
@@ -577,38 +590,11 @@ module ActiveRecord
577
590
  raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
578
591
  end
579
592
 
580
- columns_hash = connection.schema_cache.columns_hash(table_name)
593
+ columns_hash = schema_cache.columns_hash(table_name)
581
594
  columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
582
595
  @columns_hash = columns_hash.freeze
583
- @columns_hash.each do |name, column|
584
- type = connection.lookup_cast_type_from_column(column)
585
- type = _convert_type_from_options(type)
586
- define_attribute(
587
- name,
588
- type,
589
- default: column.default,
590
- user_provided_default: false
591
- )
592
- end
593
- end
594
596
 
595
- def reload_schema_from_cache
596
- @arel_table = nil
597
- @column_names = nil
598
- @symbol_column_to_string_name_hash = nil
599
- @attribute_types = nil
600
- @content_columns = nil
601
- @default_attributes = nil
602
- @column_defaults = nil
603
- @attributes_builder = nil
604
- @columns = nil
605
- @columns_hash = nil
606
- @schema_loaded = false
607
- @attribute_names = nil
608
- @yaml_encoder = nil
609
- subclasses.each do |descendant|
610
- descendant.send(:reload_schema_from_cache)
611
- end
597
+ _default_attributes # Precompute to cache DB-dependent attribute types
612
598
  end
613
599
 
614
600
  # Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -634,12 +620,14 @@ module ActiveRecord
634
620
  end
635
621
  end
636
622
 
637
- def _convert_type_from_options(type)
623
+ def type_for_column(connection, column)
624
+ type = connection.lookup_cast_type_from_column(column)
625
+
638
626
  if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
639
- type.to_immutable_string
640
- else
641
- type
627
+ type = type.to_immutable_string
642
628
  end
629
+
630
+ type
643
631
  end
644
632
  end
645
633
  end
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  class_attribute :nested_attributes_options, instance_writer: false, default: {}
16
16
  end
17
17
 
18
- # = Active Record Nested Attributes
18
+ # = Active Record Nested \Attributes
19
19
  #
20
20
  # Nested attributes allow you to save attributes on associated records
21
21
  # through the parent. By default nested attribute updating is turned off
@@ -280,6 +280,24 @@ module ActiveRecord
280
280
  # member = Member.new
281
281
  # member.avatar_attributes = {icon: 'sad'}
282
282
  # member.avatar.width # => 200
283
+ #
284
+ # === Creating forms with nested attributes
285
+ #
286
+ # Use ActionView::Helpers::FormHelper#fields_for to create form elements for
287
+ # nested attributes.
288
+ #
289
+ # Integration test params should reflect the structure of the form. For
290
+ # example:
291
+ #
292
+ # post members_path, params: {
293
+ # member: {
294
+ # name: 'joe',
295
+ # posts_attributes: {
296
+ # '0' => { title: 'Foo' },
297
+ # '1' => { title: 'Bar' }
298
+ # }
299
+ # }
300
+ # }
283
301
  module ClassMethods
284
302
  REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
285
303
 
@@ -289,7 +307,7 @@ module ActiveRecord
289
307
  # [:allow_destroy]
290
308
  # If true, destroys any members from the attributes hash with a
291
309
  # <tt>_destroy</tt> key and a value that evaluates to +true+
292
- # (e.g. 1, '1', true, or 'true'). This option is off by default.
310
+ # (e.g. 1, '1', true, or 'true'). This option is false by default.
293
311
  # [:reject_if]
294
312
  # Allows you to specify a Proc or a Symbol pointing to a method
295
313
  # that checks whether a record should be built for a certain attribute
@@ -314,11 +332,11 @@ module ActiveRecord
314
332
  # nested attributes are going to be used when an associated record already
315
333
  # exists. In general, an existing record may either be updated with the
316
334
  # new set of attribute values or be replaced by a wholly new record
317
- # containing those values. By default the +:update_only+ option is +false+
335
+ # containing those values. By default the +:update_only+ option is false
318
336
  # and the nested attributes are used to update the existing record only
319
337
  # if they include the record's <tt>:id</tt> value. Otherwise a new
320
338
  # record will be instantiated and used to replace the existing one.
321
- # However if the +:update_only+ option is +true+, the nested attributes
339
+ # However if the +:update_only+ option is true, the nested attributes
322
340
  # are used to update the record's attributes always, regardless of
323
341
  # whether the <tt>:id</tt> is present. The option is ignored for collection
324
342
  # associations.
@@ -375,11 +393,11 @@ module ActiveRecord
375
393
  end
376
394
  end
377
395
 
378
- # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
396
+ # Returns ActiveRecord::AutosaveAssociation#marked_for_destruction? It's
379
397
  # used in conjunction with fields_for to build a form element for the
380
398
  # destruction of this association.
381
399
  #
382
- # See ActionView::Helpers::FormHelper::fields_for for more info.
400
+ # See ActionView::Helpers::FormHelper#fields_for for more info.
383
401
  def _destroy
384
402
  marked_for_destruction?
385
403
  end
@@ -403,10 +421,15 @@ module ActiveRecord
403
421
  # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
404
422
  # then the existing record will be marked for destruction.
405
423
  def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
406
- options = nested_attributes_options[association_name]
407
424
  if attributes.respond_to?(:permitted?)
408
425
  attributes = attributes.to_h
409
426
  end
427
+
428
+ unless attributes.is_a?(Hash)
429
+ raise ArgumentError, "Hash expected for `#{association_name}` attributes, got #{attributes.class.name}"
430
+ end
431
+
432
+ options = nested_attributes_options[association_name]
410
433
  attributes = attributes.with_indifferent_access
411
434
  existing_record = send(association_name)
412
435
 
@@ -468,7 +491,7 @@ module ActiveRecord
468
491
  end
469
492
 
470
493
  unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
471
- raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
494
+ raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}"
472
495
  end
473
496
 
474
497
  check_record_limit!(options[:limit], attributes_collection)
@@ -491,7 +514,7 @@ module ActiveRecord
491
514
  attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
492
515
  end
493
516
 
494
- attributes_collection.each do |attributes|
517
+ records = attributes_collection.map do |attributes|
495
518
  if attributes.respond_to?(:permitted?)
496
519
  attributes = attributes.to_h
497
520
  end
@@ -501,12 +524,12 @@ module ActiveRecord
501
524
  unless reject_new_record?(association_name, attributes)
502
525
  association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
503
526
  end
504
- elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
527
+ elsif existing_record = find_record_by_id(existing_records, attributes["id"])
505
528
  unless call_reject_if(association_name, attributes)
506
529
  # Make sure we are operating on the actual object which is in the association's
507
530
  # proxy_target array (either by finding it, or adding it if not found)
508
531
  # Take into account that the proxy_target may have changed due to callbacks
509
- target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
532
+ target_record = find_record_by_id(association.target, attributes["id"])
510
533
  if target_record
511
534
  existing_record = target_record
512
535
  else
@@ -514,11 +537,14 @@ module ActiveRecord
514
537
  end
515
538
 
516
539
  assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
540
+ existing_record
517
541
  end
518
542
  else
519
543
  raise_nested_attributes_record_not_found!(association_name, attributes["id"])
520
544
  end
521
545
  end
546
+
547
+ association.nested_attributes_target = records
522
548
  end
523
549
 
524
550
  # Takes in a limit and checks if the attributes_collection has too many
@@ -594,5 +620,16 @@ module ActiveRecord
594
620
  raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
595
621
  model, "id", record_id)
596
622
  end
623
+
624
+ def find_record_by_id(records, id)
625
+ return if records.empty?
626
+
627
+ if records.first.class.composite_primary_key?
628
+ id = Array(id).map(&:to_s)
629
+ records.find { |record| Array(record.id).map(&:to_s) == id }
630
+ else
631
+ records.find { |record| record.id.to_s == id.to_s }
632
+ end
633
+ end
597
634
  end
598
635
  end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module Normalization
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :normalized_attributes, default: Set.new
9
+
10
+ before_validation :normalize_changed_in_place_attributes
11
+ end
12
+
13
+ # Normalizes a specified attribute using its declared normalizations.
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # class User < ActiveRecord::Base
18
+ # normalizes :email, with: -> email { email.strip.downcase }
19
+ # end
20
+ #
21
+ # legacy_user = User.find(1)
22
+ # legacy_user.email # => " CRUISE-CONTROL@EXAMPLE.COM\n"
23
+ # legacy_user.normalize_attribute(:email)
24
+ # legacy_user.email # => "cruise-control@example.com"
25
+ # legacy_user.save
26
+ def normalize_attribute(name)
27
+ # Treat the value as a new, unnormalized value.
28
+ self[name] = self[name]
29
+ end
30
+
31
+ module ClassMethods
32
+ # Declares a normalization for one or more attributes. The normalization
33
+ # is applied when the attribute is assigned or updated, and the normalized
34
+ # value will be persisted to the database. The normalization is also
35
+ # applied to the corresponding keyword argument of query methods. This
36
+ # allows a record to be created and later queried using unnormalized
37
+ # values.
38
+ #
39
+ # However, to prevent confusion, the normalization will not be applied
40
+ # when the attribute is fetched from the database. This means that if a
41
+ # record was persisted before the normalization was declared, the record's
42
+ # attribute will not be normalized until either it is assigned a new
43
+ # value, or it is explicitly migrated via Normalization#normalize_attribute.
44
+ #
45
+ # Because the normalization may be applied multiple times, it should be
46
+ # _idempotent_. In other words, applying the normalization more than once
47
+ # should have the same result as applying it only once.
48
+ #
49
+ # By default, the normalization will not be applied to +nil+ values. This
50
+ # behavior can be changed with the +:apply_to_nil+ option.
51
+ #
52
+ # Be aware that if your app was created before Rails 7.1, and your app
53
+ # marshals instances of the targeted model (for example, when caching),
54
+ # then you should set ActiveRecord.marshalling_format_version to +7.1+ or
55
+ # higher via either <tt>config.load_defaults 7.1</tt> or
56
+ # <tt>config.active_record.marshalling_format_version = 7.1</tt>.
57
+ # Otherwise, +Marshal+ may attempt to serialize the normalization +Proc+
58
+ # and raise +TypeError+.
59
+ #
60
+ # ==== Options
61
+ #
62
+ # * +:with+ - Any callable object that accepts the attribute's value as
63
+ # its sole argument, and returns it normalized.
64
+ # * +:apply_to_nil+ - Whether to apply the normalization to +nil+ values.
65
+ # Defaults to +false+.
66
+ #
67
+ # ==== Examples
68
+ #
69
+ # class User < ActiveRecord::Base
70
+ # normalizes :email, with: -> email { email.strip.downcase }
71
+ # normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
72
+ # end
73
+ #
74
+ # user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
75
+ # user.email # => "cruise-control@example.com"
76
+ #
77
+ # user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
78
+ # user.email # => "cruise-control@example.com"
79
+ # user.email_before_type_cast # => "cruise-control@example.com"
80
+ #
81
+ # User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
82
+ # User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
83
+ #
84
+ # User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
85
+ # User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
86
+ #
87
+ # User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
88
+ def normalizes(*names, with:, apply_to_nil: false)
89
+ decorate_attributes(names) do |name, cast_type|
90
+ NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
91
+ end
92
+
93
+ self.normalized_attributes += names.map(&:to_sym)
94
+ end
95
+
96
+ # Normalizes a given +value+ using normalizations declared for +name+.
97
+ #
98
+ # ==== Examples
99
+ #
100
+ # class User < ActiveRecord::Base
101
+ # normalizes :email, with: -> email { email.strip.downcase }
102
+ # end
103
+ #
104
+ # User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n")
105
+ # # => "cruise-control@example.com"
106
+ def normalize_value_for(name, value)
107
+ type_for_attribute(name).cast(value)
108
+ end
109
+ end
110
+
111
+ private
112
+ def normalize_changed_in_place_attributes
113
+ self.class.normalized_attributes.each do |name|
114
+ normalize_attribute(name) if attribute_changed_in_place?(name)
115
+ end
116
+ end
117
+
118
+ class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc:
119
+ include ActiveModel::Type::SerializeCastValue
120
+
121
+ attr_reader :cast_type, :normalizer, :normalize_nil
122
+ alias :normalize_nil? :normalize_nil
123
+
124
+ def initialize(cast_type:, normalizer:, normalize_nil:)
125
+ @cast_type = cast_type
126
+ @normalizer = normalizer
127
+ @normalize_nil = normalize_nil
128
+ super(cast_type)
129
+ end
130
+
131
+ def cast(value)
132
+ normalize(super(value))
133
+ end
134
+
135
+ def serialize(value)
136
+ serialize_cast_value(cast(value))
137
+ end
138
+
139
+ def serialize_cast_value(value)
140
+ ActiveModel::Type::SerializeCastValue.serialize(cast_type, value)
141
+ end
142
+
143
+ def ==(other)
144
+ self.class == other.class &&
145
+ normalize_nil? == other.normalize_nil? &&
146
+ normalizer == other.normalizer &&
147
+ cast_type == other.cast_type
148
+ end
149
+ alias eql? ==
150
+
151
+ def hash
152
+ [self.class, cast_type, normalizer, normalize_nil?].hash
153
+ end
154
+
155
+ define_method(:inspect, Kernel.instance_method(:inspect))
156
+
157
+ private
158
+ def normalize(value)
159
+ normalizer.call(value) unless value.nil? && !normalize_nil?
160
+ end
161
+ end
162
+ end
163
+ end