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
@@ -1,29 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/enumerable"
4
- require "active_support/core_ext/hash/indifferent_access"
5
- require "active_support/core_ext/string/filters"
4
+ require "active_support/core_ext/module/delegation"
6
5
  require "active_support/parameter_filter"
7
6
  require "concurrent/map"
8
7
 
9
8
  module ActiveRecord
9
+ # = Active Record \Core
10
10
  module Core
11
11
  extend ActiveSupport::Concern
12
+ include ActiveModel::Access
12
13
 
13
14
  included do
14
15
  ##
15
16
  # :singleton-method:
16
17
  #
17
- # Accepts a logger conforming to the interface of Log4r which is then
18
- # passed on to any new database connections made and which can be
19
- # retrieved on both a class and instance level by calling +logger+.
18
+ # Accepts a logger conforming to the interface of Log4r or the default
19
+ # Ruby +Logger+ class, which is then passed on to any new database
20
+ # connections made. You can retrieve this logger by calling +logger+ on
21
+ # either an Active Record model class or an Active Record model instance.
20
22
  class_attribute :logger, instance_writer: false
21
23
 
24
+ class_attribute :_destroy_association_async_job, instance_accessor: false, default: "ActiveRecord::DestroyAssociationAsyncJob"
25
+
26
+ # The job class used to destroy associations in the background.
27
+ def self.destroy_association_async_job
28
+ if _destroy_association_async_job.is_a?(String)
29
+ self._destroy_association_async_job = _destroy_association_async_job.constantize
30
+ end
31
+ _destroy_association_async_job
32
+ rescue NameError => error
33
+ raise NameError, "Unable to load destroy_association_async_job: #{error.message}"
34
+ end
35
+
36
+ singleton_class.alias_method :destroy_association_async_job=, :_destroy_association_async_job=
37
+ delegate :destroy_association_async_job, to: :class
38
+
22
39
  ##
23
40
  # :singleton-method:
24
41
  #
25
- # Specifies the job used to destroy associations in the background
26
- class_attribute :destroy_association_async_job, instance_writer: false, instance_predicate: false, default: false
42
+ # Specifies the maximum number of records that will be destroyed in a
43
+ # single background job by the <tt>dependent: :destroy_async</tt>
44
+ # association option. When +nil+ (default), all dependent records will be
45
+ # destroyed in a single background job. If specified, the records to be
46
+ # destroyed will be split into multiple background jobs.
47
+ class_attribute :destroy_association_async_batch_size, instance_writer: false, instance_predicate: false, default: nil
27
48
 
28
49
  ##
29
50
  # Contains the database configuration - as is typically stored in config/database.yml -
@@ -33,26 +54,26 @@ module ActiveRecord
33
54
  #
34
55
  # development:
35
56
  # adapter: sqlite3
36
- # database: db/development.sqlite3
57
+ # database: storage/development.sqlite3
37
58
  #
38
59
  # production:
39
60
  # adapter: sqlite3
40
- # database: db/production.sqlite3
61
+ # database: storage/production.sqlite3
41
62
  #
42
63
  # ...would result in ActiveRecord::Base.configurations to look like this:
43
64
  #
44
65
  # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
45
66
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
46
- # @name="primary", @config={adapter: "sqlite3", database: "db/development.sqlite3"}>,
67
+ # @name="primary", @config={adapter: "sqlite3", database: "storage/development.sqlite3"}>,
47
68
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
48
- # @name="primary", @config={adapter: "sqlite3", database: "db/production.sqlite3"}>
69
+ # @name="primary", @config={adapter: "sqlite3", database: "storage/production.sqlite3"}>
49
70
  # ]>
50
71
  def self.configurations=(config)
51
72
  @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
52
73
  end
53
74
  self.configurations = {}
54
75
 
55
- # Returns fully resolved ActiveRecord::DatabaseConfigurations object
76
+ # Returns a fully resolved ActiveRecord::DatabaseConfigurations object.
56
77
  def self.configurations
57
78
  @@configurations
58
79
  end
@@ -71,6 +92,8 @@ module ActiveRecord
71
92
 
72
93
  class_attribute :has_many_inversing, instance_accessor: false, default: false
73
94
 
95
+ class_attribute :run_commit_callbacks_on_first_saved_instances_in_transaction, instance_accessor: false, default: true
96
+
74
97
  class_attribute :default_connection_handler, instance_writer: false
75
98
 
76
99
  class_attribute :default_role, instance_writer: false
@@ -79,6 +102,21 @@ module ActiveRecord
79
102
 
80
103
  class_attribute :shard_selector, instance_accessor: false, default: nil
81
104
 
105
+ ##
106
+ # :singleton-method:
107
+ #
108
+ # Specifies the attributes that will be included in the output of the
109
+ # #inspect method:
110
+ #
111
+ # Post.attributes_for_inspect = [:id, :title]
112
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"
113
+ #
114
+ # When set to `:all` inspect will list all the record's attributes:
115
+ #
116
+ # Post.attributes_for_inspect = :all
117
+ # Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
118
+ class_attribute :attributes_for_inspect, instance_accessor: false, default: :all
119
+
82
120
  def self.application_record_class? # :nodoc:
83
121
  if ActiveRecord.application_record_class
84
122
  self == ActiveRecord.application_record_class
@@ -99,33 +137,6 @@ module ActiveRecord
99
137
  ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
100
138
  end
101
139
 
102
- def self.connection_handlers
103
- if ActiveRecord.legacy_connection_handling
104
- else
105
- raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers."
106
- end
107
-
108
- @@connection_handlers ||= {}
109
- end
110
-
111
- def self.connection_handlers=(handlers)
112
- if ActiveRecord.legacy_connection_handling
113
- ActiveSupport::Deprecation.warn(<<~MSG)
114
- Using legacy connection handling is deprecated. Please set
115
- `legacy_connection_handling` to `false` in your application.
116
-
117
- The new connection handling does not support `connection_handlers`
118
- getter and setter.
119
-
120
- Read more about how to migrate at: https://guides.rubyonrails.org/active_record_multiple_databases.html#migrate-to-the-new-connection-handling
121
- MSG
122
- else
123
- raise NotImplementedError, "The new connection handling does not support multiple connection handlers."
124
- end
125
-
126
- @@connection_handlers = handlers
127
- end
128
-
129
140
  def self.asynchronous_queries_session # :nodoc:
130
141
  asynchronous_queries_tracker.current_session
131
142
  end
@@ -145,16 +156,12 @@ module ActiveRecord
145
156
  # ActiveRecord::Base.current_role #=> :reading
146
157
  # end
147
158
  def self.current_role
148
- if ActiveRecord.legacy_connection_handling
149
- connection_handlers.key(connection_handler) || default_role
150
- else
151
- connected_to_stack.reverse_each do |hash|
152
- return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
153
- return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
154
- end
155
-
156
- default_role
159
+ connected_to_stack.reverse_each do |hash|
160
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
161
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
157
162
  end
163
+
164
+ default_role
158
165
  end
159
166
 
160
167
  # Returns the symbol representing the current connected shard.
@@ -186,16 +193,12 @@ module ActiveRecord
186
193
  # ActiveRecord::Base.current_preventing_writes #=> false
187
194
  # end
188
195
  def self.current_preventing_writes
189
- if ActiveRecord.legacy_connection_handling
190
- connection_handler.prevent_writes
191
- else
192
- connected_to_stack.reverse_each do |hash|
193
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
194
- return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
195
- end
196
-
197
- false
196
+ connected_to_stack.reverse_each do |hash|
197
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
198
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
198
199
  end
200
+
201
+ false
199
202
  end
200
203
 
201
204
  def self.connected_to_stack # :nodoc:
@@ -252,19 +255,6 @@ module ActiveRecord
252
255
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
253
256
  end
254
257
 
255
- def inherited(child_class) # :nodoc:
256
- # initialize cache at class definition for thread safety
257
- child_class.initialize_find_by_cache
258
- unless child_class.base_class?
259
- klass = self
260
- until klass.base_class?
261
- klass.initialize_find_by_cache
262
- klass = klass.superclass
263
- end
264
- end
265
- super
266
- end
267
-
268
258
  def find(*ids) # :nodoc:
269
259
  # We don't have cache keys for this stuff yet
270
260
  return super unless ids.length == 1
@@ -274,14 +264,8 @@ module ActiveRecord
274
264
 
275
265
  return super if StatementCache.unsupported_value?(id)
276
266
 
277
- key = primary_key
278
-
279
- statement = cached_find_by_statement(key) { |params|
280
- where(key => params.bind).limit(1)
281
- }
282
-
283
- statement.execute([id], connection).first ||
284
- raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
267
+ cached_find_by([primary_key], [id]) ||
268
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
285
269
  end
286
270
 
287
271
  def find_by(*args) # :nodoc:
@@ -303,58 +287,38 @@ module ActiveRecord
303
287
  elsif reflection.belongs_to? && !reflection.polymorphic?
304
288
  key = reflection.join_foreign_key
305
289
  pkey = reflection.join_primary_key
306
- value = value.public_send(pkey) if value.respond_to?(pkey)
290
+
291
+ if pkey.is_a?(Array)
292
+ if pkey.all? { |attribute| value.respond_to?(attribute) }
293
+ value = pkey.map do |attribute|
294
+ if attribute == "id"
295
+ value.id_value
296
+ else
297
+ value.public_send(attribute)
298
+ end
299
+ end
300
+ composite_primary_key = true
301
+ end
302
+ else
303
+ value = value.public_send(pkey) if value.respond_to?(pkey)
304
+ end
307
305
  end
308
306
 
309
- if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
307
+ if !composite_primary_key &&
308
+ (!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
310
309
  return super
311
310
  end
312
311
 
313
312
  h[key] = value
314
313
  end
315
314
 
316
- keys = hash.keys
317
- statement = cached_find_by_statement(keys) { |params|
318
- wheres = keys.index_with { params.bind }
319
- where(wheres).limit(1)
320
- }
321
-
322
- begin
323
- statement.execute(hash.values, connection).first
324
- rescue TypeError
325
- raise ActiveRecord::StatementInvalid
326
- end
315
+ cached_find_by(hash.keys, hash.values)
327
316
  end
328
317
 
329
318
  def find_by!(*args) # :nodoc:
330
319
  find_by(*args) || where(*args).raise_record_not_found_exception!
331
320
  end
332
321
 
333
- %w(
334
- reading_role writing_role legacy_connection_handling default_timezone index_nested_attribute_errors
335
- verbose_query_logs queues warn_on_records_fetched_greater_than maintain_test_schema
336
- application_record_class action_on_strict_loading_violation schema_format error_on_ignored_order
337
- timestamped_migrations dump_schema_after_migration dump_schemas suppress_multiple_database_warning
338
- ).each do |attr|
339
- module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
340
- def #{attr}
341
- ActiveSupport::Deprecation.warn(<<~MSG)
342
- ActiveRecord::Base.#{attr} is deprecated and will be removed in Rails 7.1.
343
- Use `ActiveRecord.#{attr}` instead.
344
- MSG
345
- ActiveRecord.#{attr}
346
- end
347
-
348
- def #{attr}=(value)
349
- ActiveSupport::Deprecation.warn(<<~MSG)
350
- ActiveRecord::Base.#{attr}= is deprecated and will be removed in Rails 7.1.
351
- Use `ActiveRecord.#{attr}=` instead.
352
- MSG
353
- ActiveRecord.#{attr} = value
354
- end
355
- RUBY
356
- end
357
-
358
322
  def initialize_generated_modules # :nodoc:
359
323
  generated_association_methods
360
324
  end
@@ -371,10 +335,10 @@ module ActiveRecord
371
335
 
372
336
  # Returns columns which shouldn't be exposed while calling +#inspect+.
373
337
  def filter_attributes
374
- if defined?(@filter_attributes)
375
- @filter_attributes
376
- else
338
+ if @filter_attributes.nil?
377
339
  superclass.filter_attributes
340
+ else
341
+ @filter_attributes
378
342
  end
379
343
  end
380
344
 
@@ -385,24 +349,24 @@ module ActiveRecord
385
349
  end
386
350
 
387
351
  def inspection_filter # :nodoc:
388
- if defined?(@filter_attributes)
352
+ if @filter_attributes.nil?
353
+ superclass.inspection_filter
354
+ else
389
355
  @inspection_filter ||= begin
390
356
  mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
391
357
  ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask)
392
358
  end
393
- else
394
- superclass.inspection_filter
395
359
  end
396
360
  end
397
361
 
398
362
  # Returns a string like 'Post(id:integer, title:string, body:text)'
399
363
  def inspect # :nodoc:
400
- if self == Base
364
+ if self == Base || singleton_class?
401
365
  super
402
366
  elsif abstract_class?
403
367
  "#{super}(abstract)"
404
- elsif !connected?
405
- "#{super} (call '#{super}.connection' to establish a connection)"
368
+ elsif !schema_loaded? && !connected?
369
+ "#{super} (call '#{super}.load_schema' to load schema informations)"
406
370
  elsif table_exists?
407
371
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
408
372
  "#{super}(#{attr_list})"
@@ -411,12 +375,7 @@ module ActiveRecord
411
375
  end
412
376
  end
413
377
 
414
- # Override the default class equality method to provide support for decorated models.
415
- def ===(object) # :nodoc:
416
- object.is_a?(self)
417
- end
418
-
419
- # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
378
+ # Returns an instance of +Arel::Table+ loaded with the current table name.
420
379
  def arel_table # :nodoc:
421
380
  @arel_table ||= Arel::Table.new(table_name, klass: self)
422
381
  end
@@ -429,12 +388,34 @@ module ActiveRecord
429
388
  TypeCaster::Map.new(self)
430
389
  end
431
390
 
432
- def cached_find_by_statement(key, &block) # :nodoc:
391
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
433
392
  cache = @find_by_statement_cache[connection.prepared_statements]
434
393
  cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
435
394
  end
436
395
 
437
396
  private
397
+ def inherited(subclass)
398
+ super
399
+
400
+ # initialize cache at class definition for thread safety
401
+ subclass.initialize_find_by_cache
402
+ unless subclass.base_class?
403
+ klass = self
404
+ until klass.base_class?
405
+ klass.initialize_find_by_cache
406
+ klass = klass.superclass
407
+ end
408
+ end
409
+
410
+ subclass.class_eval do
411
+ @arel_table = nil
412
+ @predicate_builder = nil
413
+ @inspection_filter = nil
414
+ @filter_attributes ||= nil
415
+ @generated_association_methods ||= nil
416
+ end
417
+ end
418
+
438
419
  def relation
439
420
  relation = Relation.create(self)
440
421
 
@@ -448,6 +429,27 @@ module ActiveRecord
448
429
  def table_metadata
449
430
  TableMetadata.new(self, arel_table)
450
431
  end
432
+
433
+ def cached_find_by(keys, values)
434
+ with_connection do |connection|
435
+ statement = cached_find_by_statement(connection, keys) { |params|
436
+ wheres = keys.index_with do |key|
437
+ if key.is_a?(Array)
438
+ [key.map { params.bind }]
439
+ else
440
+ params.bind
441
+ end
442
+ end
443
+ where(wheres).limit(1)
444
+ }
445
+
446
+ begin
447
+ statement.execute(values.flatten, connection, allow_retry: true).first
448
+ rescue TypeError
449
+ raise ActiveRecord::StatementInvalid
450
+ end
451
+ end
452
+ end
451
453
  end
452
454
 
453
455
  # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
@@ -455,7 +457,7 @@ module ActiveRecord
455
457
  # In both instances, valid attribute keys are determined by the column names of the associated table --
456
458
  # hence you can't have attributes that aren't part of the table columns.
457
459
  #
458
- # ==== Example:
460
+ # ==== Example
459
461
  # # Instantiates a single new object
460
462
  # User.new(first_name: 'Jamie')
461
463
  def initialize(attributes = nil)
@@ -465,7 +467,7 @@ module ActiveRecord
465
467
  init_internals
466
468
  initialize_internals_callback
467
469
 
468
- assign_attributes(attributes) if attributes
470
+ super
469
471
 
470
472
  yield self if block_given?
471
473
  _run_initialize_callbacks
@@ -533,12 +535,17 @@ module ActiveRecord
533
535
  # only, not its associations. The extent of a "deep" copy is application
534
536
  # specific and is therefore left to the application to implement according
535
537
  # to its need.
536
- # The dup method does not preserve the timestamps (created|updated)_(at|on).
538
+ # The dup method does not preserve the timestamps (created|updated)_(at|on)
539
+ # and locking column.
537
540
 
538
541
  ##
539
542
  def initialize_dup(other) # :nodoc:
540
543
  @attributes = @attributes.deep_dup
541
- @attributes.reset(@primary_key)
544
+ if self.class.composite_primary_key?
545
+ @primary_key.each { |key| @attributes.reset(key) }
546
+ else
547
+ @attributes.reset(@primary_key)
548
+ end
542
549
 
543
550
  _run_initialize_callbacks
544
551
 
@@ -568,6 +575,35 @@ module ActiveRecord
568
575
  coder["active_record_yaml_version"] = 2
569
576
  end
570
577
 
578
+ ##
579
+ # :method: slice
580
+ #
581
+ # :call-seq: slice(*methods)
582
+ #
583
+ # Returns a hash of the given methods with their names as keys and returned
584
+ # values as values.
585
+ #
586
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
587
+ # topic.slice(:title, :author_name)
588
+ # => { "title" => "Budget", "author_name" => "Jason" }
589
+ #
590
+ #--
591
+ # Implemented by ActiveModel::Access#slice.
592
+
593
+ ##
594
+ # :method: values_at
595
+ #
596
+ # :call-seq: values_at(*methods)
597
+ #
598
+ # Returns an array of the values returned by the given methods.
599
+ #
600
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
601
+ # topic.values_at(:title, :author_name)
602
+ # => ["Budget", "Jason"]
603
+ #
604
+ #--
605
+ # Implemented by ActiveModel::Access#values_at.
606
+
571
607
  # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
572
608
  # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
573
609
  #
@@ -580,7 +616,7 @@ module ActiveRecord
580
616
  def ==(comparison_object)
581
617
  super ||
582
618
  comparison_object.instance_of?(self.class) &&
583
- !id.nil? &&
619
+ primary_key_values_present? &&
584
620
  comparison_object.id == id
585
621
  end
586
622
  alias :eql? :==
@@ -590,7 +626,7 @@ module ActiveRecord
590
626
  def hash
591
627
  id = self.id
592
628
 
593
- if id
629
+ if primary_key_values_present?
594
630
  self.class.hash ^ id.hash
595
631
  else
596
632
  super
@@ -642,25 +678,33 @@ module ActiveRecord
642
678
  #
643
679
  # user = User.first
644
680
  # user.strict_loading! # => true
645
- # user.comments
681
+ # user.address.city
682
+ # => ActiveRecord::StrictLoadingViolationError
683
+ # user.comments.to_a
646
684
  # => ActiveRecord::StrictLoadingViolationError
647
685
  #
648
- # === Parameters:
686
+ # ==== Parameters
649
687
  #
650
- # * value - Boolean specifying whether to enable or disable strict loading.
651
- # * mode - Symbol specifying strict loading mode. Defaults to :all. Using
652
- # :n_plus_one_only mode will only raise an error if an association
653
- # that will lead to an n plus one query is lazily loaded.
688
+ # * +value+ - Boolean specifying whether to enable or disable strict loading.
689
+ # * <tt>:mode</tt> - Symbol specifying strict loading mode. Defaults to :all. Using
690
+ # :n_plus_one_only mode will only raise an error if an association that
691
+ # will lead to an n plus one query is lazily loaded.
654
692
  #
655
- # === Example:
693
+ # ==== Examples
656
694
  #
657
695
  # user = User.first
658
696
  # user.strict_loading!(false) # => false
659
- # user.comments
660
- # => #<ActiveRecord::Associations::CollectionProxy>
697
+ # user.address.city # => "Tatooine"
698
+ # user.comments.to_a # => [#<Comment:0x00...]
699
+ #
700
+ # user.strict_loading!(mode: :n_plus_one_only)
701
+ # user.address.city # => "Tatooine"
702
+ # user.comments.to_a # => [#<Comment:0x00...]
703
+ # user.comments.first.ratings.to_a
704
+ # => ActiveRecord::StrictLoadingViolationError
661
705
  def strict_loading!(value = true, mode: :all)
662
706
  unless [:all, :n_plus_one_only].include?(mode)
663
- raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only]."
707
+ raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
664
708
  end
665
709
 
666
710
  @strict_loading_mode = mode
@@ -674,7 +718,16 @@ module ActiveRecord
674
718
  @strict_loading_mode == :n_plus_one_only
675
719
  end
676
720
 
721
+ # Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
722
+ def strict_loading_all?
723
+ @strict_loading_mode == :all
724
+ end
725
+
677
726
  # Marks this record as read only.
727
+ #
728
+ # customer = Customer.first
729
+ # customer.readonly!
730
+ # customer.save # Raises an ActiveRecord::ReadOnlyRecord
678
731
  def readonly!
679
732
  @readonly = true
680
733
  end
@@ -683,21 +736,28 @@ module ActiveRecord
683
736
  self.class.connection_handler
684
737
  end
685
738
 
686
- # Returns the contents of the record as a nicely formatted string.
739
+ # Returns the attributes of the record as a nicely formatted string.
740
+ #
741
+ # Post.first.inspect
742
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
743
+ #
744
+ # The attributes can be limited by setting <tt>.attributes_for_inspect</tt>.
745
+ #
746
+ # Post.attributes_for_inspect = [:id, :title]
747
+ # Post.first.inspect
748
+ # #=> "#<Post id: 1, title: "Hello, World!">"
687
749
  def inspect
688
- # We check defined?(@attributes) not to issue warnings if the object is
689
- # allocated but not initialized.
690
- inspection = if defined?(@attributes) && @attributes
691
- self.class.attribute_names.filter_map do |name|
692
- if _has_attribute?(name)
693
- "#{name}: #{attribute_for_inspect(name)}"
694
- end
695
- end.join(", ")
696
- else
697
- "not initialized"
698
- end
750
+ inspect_with_attributes(attributes_for_inspect)
751
+ end
699
752
 
700
- "#<#{self.class} #{inspection}>"
753
+ # Returns all attributes of the record as a nicely formatted string,
754
+ # ignoring <tt>.attributes_for_inspect</tt>.
755
+ #
756
+ # Post.first.full_inspect
757
+ # #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"
758
+ #
759
+ def full_inspect
760
+ inspect_with_attributes(all_attributes_for_inspect)
701
761
  end
702
762
 
703
763
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -705,17 +765,17 @@ module ActiveRecord
705
765
  def pretty_print(pp)
706
766
  return super if custom_inspect_method_defined?
707
767
  pp.object_address_group(self) do
708
- if defined?(@attributes) && @attributes
709
- attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
768
+ if @attributes
769
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
710
770
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
771
+ attr_name = attr_name.to_s
711
772
  pp.breakable " "
712
773
  pp.group(1) do
713
774
  pp.text attr_name
714
775
  pp.text ":"
715
776
  pp.breakable
716
- value = _read_attribute(attr_name)
717
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
718
- pp.pp value
777
+ value = attribute_for_inspect(attr_name)
778
+ pp.text value
719
779
  end
720
780
  end
721
781
  else
@@ -725,16 +785,6 @@ module ActiveRecord
725
785
  end
726
786
  end
727
787
 
728
- # Returns a hash of the given methods with their names as keys and returned values as values.
729
- def slice(*methods)
730
- methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
731
- end
732
-
733
- # Returns an array of the values returned by the given methods.
734
- def values_at(*methods)
735
- methods.flatten.map! { |method| public_send(method) }
736
- end
737
-
738
788
  private
739
789
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
740
790
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
@@ -782,5 +832,30 @@ module ActiveRecord
782
832
  def inspection_filter
783
833
  self.class.inspection_filter
784
834
  end
835
+
836
+ def inspect_with_attributes(attributes_to_list)
837
+ inspection = if @attributes
838
+ attributes_to_list.filter_map do |name|
839
+ name = name.to_s
840
+ if _has_attribute?(name)
841
+ "#{name}: #{attribute_for_inspect(name)}"
842
+ end
843
+ end.join(", ")
844
+ else
845
+ "not initialized"
846
+ end
847
+
848
+ "#<#{self.class} #{inspection}>"
849
+ end
850
+
851
+ def attributes_for_inspect
852
+ self.class.attributes_for_inspect == :all ? all_attributes_for_inspect : self.class.attributes_for_inspect
853
+ end
854
+
855
+ def all_attributes_for_inspect
856
+ return [] unless @attributes
857
+
858
+ attribute_names
859
+ end
785
860
  end
786
861
  end