activerecord 7.0.8 → 7.2.0

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 (277) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +530 -2004
  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 +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +5 -5
  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 +328 -471
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +7 -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 +58 -45
  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 +10 -24
  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 +317 -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 +188 -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 +306 -128
  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 +274 -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 +368 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +364 -198
  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 +217 -174
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +7 -2
  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 +44 -20
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +45 -10
  130. data/lib/active_record/encryption/encryptor.rb +17 -2
  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/message_pack_message_serializer.rb +76 -0
  135. data/lib/active_record/encryption/message_serializer.rb +6 -0
  136. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  137. data/lib/active_record/encryption/properties.rb +3 -3
  138. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  139. data/lib/active_record/encryption/scheme.rb +22 -21
  140. data/lib/active_record/encryption.rb +1 -0
  141. data/lib/active_record/enum.rb +122 -29
  142. data/lib/active_record/errors.rb +151 -31
  143. data/lib/active_record/explain.rb +21 -12
  144. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  145. data/lib/active_record/fixture_set/render_context.rb +2 -0
  146. data/lib/active_record/fixture_set/table_row.rb +29 -8
  147. data/lib/active_record/fixtures.rb +167 -97
  148. data/lib/active_record/future_result.rb +47 -8
  149. data/lib/active_record/gem_version.rb +3 -3
  150. data/lib/active_record/inheritance.rb +34 -18
  151. data/lib/active_record/insert_all.rb +72 -22
  152. data/lib/active_record/integration.rb +11 -8
  153. data/lib/active_record/internal_metadata.rb +124 -20
  154. data/lib/active_record/locking/optimistic.rb +8 -7
  155. data/lib/active_record/locking/pessimistic.rb +5 -2
  156. data/lib/active_record/log_subscriber.rb +18 -22
  157. data/lib/active_record/marshalling.rb +56 -0
  158. data/lib/active_record/message_pack.rb +124 -0
  159. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  160. data/lib/active_record/middleware/database_selector.rb +6 -8
  161. data/lib/active_record/middleware/shard_selector.rb +3 -1
  162. data/lib/active_record/migration/command_recorder.rb +106 -8
  163. data/lib/active_record/migration/compatibility.rb +147 -5
  164. data/lib/active_record/migration/default_strategy.rb +22 -0
  165. data/lib/active_record/migration/execution_strategy.rb +19 -0
  166. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  167. data/lib/active_record/migration.rb +234 -117
  168. data/lib/active_record/model_schema.rb +88 -103
  169. data/lib/active_record/nested_attributes.rb +35 -9
  170. data/lib/active_record/normalization.rb +163 -0
  171. data/lib/active_record/persistence.rb +168 -339
  172. data/lib/active_record/promise.rb +84 -0
  173. data/lib/active_record/query_cache.rb +19 -25
  174. data/lib/active_record/query_logs.rb +92 -52
  175. data/lib/active_record/query_logs_formatter.rb +41 -0
  176. data/lib/active_record/querying.rb +33 -8
  177. data/lib/active_record/railtie.rb +135 -86
  178. data/lib/active_record/railties/controller_runtime.rb +22 -7
  179. data/lib/active_record/railties/databases.rake +145 -154
  180. data/lib/active_record/railties/job_runtime.rb +23 -0
  181. data/lib/active_record/readonly_attributes.rb +32 -5
  182. data/lib/active_record/reflection.rb +259 -68
  183. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  184. data/lib/active_record/relation/batches.rb +196 -61
  185. data/lib/active_record/relation/calculations.rb +249 -92
  186. data/lib/active_record/relation/delegation.rb +30 -19
  187. data/lib/active_record/relation/finder_methods.rb +93 -18
  188. data/lib/active_record/relation/merger.rb +6 -6
  189. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  190. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  191. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  192. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  193. data/lib/active_record/relation/predicate_builder.rb +28 -16
  194. data/lib/active_record/relation/query_attribute.rb +2 -1
  195. data/lib/active_record/relation/query_methods.rb +548 -94
  196. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  197. data/lib/active_record/relation/spawn_methods.rb +5 -4
  198. data/lib/active_record/relation/where_clause.rb +7 -19
  199. data/lib/active_record/relation.rb +580 -90
  200. data/lib/active_record/result.rb +49 -48
  201. data/lib/active_record/runtime_registry.rb +63 -1
  202. data/lib/active_record/sanitization.rb +70 -25
  203. data/lib/active_record/schema.rb +8 -7
  204. data/lib/active_record/schema_dumper.rb +63 -14
  205. data/lib/active_record/schema_migration.rb +75 -24
  206. data/lib/active_record/scoping/default.rb +15 -5
  207. data/lib/active_record/scoping/named.rb +2 -2
  208. data/lib/active_record/scoping.rb +2 -1
  209. data/lib/active_record/secure_password.rb +60 -0
  210. data/lib/active_record/secure_token.rb +21 -3
  211. data/lib/active_record/signed_id.rb +27 -6
  212. data/lib/active_record/statement_cache.rb +7 -7
  213. data/lib/active_record/store.rb +8 -8
  214. data/lib/active_record/suppressor.rb +3 -1
  215. data/lib/active_record/table_metadata.rb +1 -1
  216. data/lib/active_record/tasks/database_tasks.rb +180 -119
  217. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  218. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  219. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  220. data/lib/active_record/test_fixtures.rb +170 -155
  221. data/lib/active_record/testing/query_assertions.rb +121 -0
  222. data/lib/active_record/timestamp.rb +31 -17
  223. data/lib/active_record/token_for.rb +123 -0
  224. data/lib/active_record/touch_later.rb +12 -7
  225. data/lib/active_record/transaction.rb +132 -0
  226. data/lib/active_record/transactions.rb +106 -24
  227. data/lib/active_record/translation.rb +0 -2
  228. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  229. data/lib/active_record/type/internal/timezone.rb +7 -2
  230. data/lib/active_record/type/serialized.rb +1 -3
  231. data/lib/active_record/type/time.rb +4 -0
  232. data/lib/active_record/type_caster/connection.rb +4 -4
  233. data/lib/active_record/validations/absence.rb +1 -1
  234. data/lib/active_record/validations/associated.rb +9 -3
  235. data/lib/active_record/validations/numericality.rb +5 -4
  236. data/lib/active_record/validations/presence.rb +5 -28
  237. data/lib/active_record/validations/uniqueness.rb +60 -11
  238. data/lib/active_record/validations.rb +12 -5
  239. data/lib/active_record/version.rb +1 -1
  240. data/lib/active_record.rb +247 -33
  241. data/lib/arel/alias_predication.rb +1 -1
  242. data/lib/arel/collectors/bind.rb +2 -0
  243. data/lib/arel/collectors/composite.rb +7 -0
  244. data/lib/arel/collectors/sql_string.rb +1 -1
  245. data/lib/arel/collectors/substitute_binds.rb +1 -1
  246. data/lib/arel/errors.rb +10 -0
  247. data/lib/arel/factory_methods.rb +4 -0
  248. data/lib/arel/nodes/binary.rb +6 -7
  249. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  250. data/lib/arel/nodes/cte.rb +36 -0
  251. data/lib/arel/nodes/fragments.rb +35 -0
  252. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  253. data/lib/arel/nodes/leading_join.rb +8 -0
  254. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  255. data/lib/arel/nodes/node.rb +115 -5
  256. data/lib/arel/nodes/sql_literal.rb +13 -0
  257. data/lib/arel/nodes/table_alias.rb +4 -0
  258. data/lib/arel/nodes.rb +6 -2
  259. data/lib/arel/predications.rb +3 -1
  260. data/lib/arel/select_manager.rb +1 -1
  261. data/lib/arel/table.rb +9 -5
  262. data/lib/arel/tree_manager.rb +8 -3
  263. data/lib/arel/update_manager.rb +2 -1
  264. data/lib/arel/visitors/dot.rb +1 -0
  265. data/lib/arel/visitors/mysql.rb +17 -5
  266. data/lib/arel/visitors/postgresql.rb +1 -12
  267. data/lib/arel/visitors/to_sql.rb +112 -34
  268. data/lib/arel/visitors/visitor.rb +2 -2
  269. data/lib/arel.rb +21 -3
  270. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  271. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  272. data/lib/rails/generators/active_record/migration.rb +3 -1
  273. data/lib/rails/generators/active_record/model/USAGE +113 -0
  274. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  275. metadata +56 -14
  276. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  277. 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,9 @@ module ActiveRecord
79
102
 
80
103
  class_attribute :shard_selector, instance_accessor: false, default: nil
81
104
 
105
+ # Specifies the attributes that will be included in the output of the #inspect method
106
+ class_attribute :attributes_for_inspect, instance_accessor: false, default: [:id]
107
+
82
108
  def self.application_record_class? # :nodoc:
83
109
  if ActiveRecord.application_record_class
84
110
  self == ActiveRecord.application_record_class
@@ -99,33 +125,6 @@ module ActiveRecord
99
125
  ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
100
126
  end
101
127
 
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
128
  def self.asynchronous_queries_session # :nodoc:
130
129
  asynchronous_queries_tracker.current_session
131
130
  end
@@ -145,16 +144,12 @@ module ActiveRecord
145
144
  # ActiveRecord::Base.current_role #=> :reading
146
145
  # end
147
146
  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
147
+ connected_to_stack.reverse_each do |hash|
148
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
149
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
157
150
  end
151
+
152
+ default_role
158
153
  end
159
154
 
160
155
  # Returns the symbol representing the current connected shard.
@@ -186,16 +181,12 @@ module ActiveRecord
186
181
  # ActiveRecord::Base.current_preventing_writes #=> false
187
182
  # end
188
183
  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
184
+ connected_to_stack.reverse_each do |hash|
185
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
186
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
198
187
  end
188
+
189
+ false
199
190
  end
200
191
 
201
192
  def self.connected_to_stack # :nodoc:
@@ -252,19 +243,6 @@ module ActiveRecord
252
243
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
253
244
  end
254
245
 
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
246
  def find(*ids) # :nodoc:
269
247
  # We don't have cache keys for this stuff yet
270
248
  return super unless ids.length == 1
@@ -274,14 +252,8 @@ module ActiveRecord
274
252
 
275
253
  return super if StatementCache.unsupported_value?(id)
276
254
 
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))
255
+ cached_find_by([primary_key], [id]) ||
256
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
285
257
  end
286
258
 
287
259
  def find_by(*args) # :nodoc:
@@ -303,58 +275,38 @@ module ActiveRecord
303
275
  elsif reflection.belongs_to? && !reflection.polymorphic?
304
276
  key = reflection.join_foreign_key
305
277
  pkey = reflection.join_primary_key
306
- value = value.public_send(pkey) if value.respond_to?(pkey)
278
+
279
+ if pkey.is_a?(Array)
280
+ if pkey.all? { |attribute| value.respond_to?(attribute) }
281
+ value = pkey.map do |attribute|
282
+ if attribute == "id"
283
+ value.id_value
284
+ else
285
+ value.public_send(attribute)
286
+ end
287
+ end
288
+ composite_primary_key = true
289
+ end
290
+ else
291
+ value = value.public_send(pkey) if value.respond_to?(pkey)
292
+ end
307
293
  end
308
294
 
309
- if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
295
+ if !composite_primary_key &&
296
+ (!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
310
297
  return super
311
298
  end
312
299
 
313
300
  h[key] = value
314
301
  end
315
302
 
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
303
+ cached_find_by(hash.keys, hash.values)
327
304
  end
328
305
 
329
306
  def find_by!(*args) # :nodoc:
330
307
  find_by(*args) || where(*args).raise_record_not_found_exception!
331
308
  end
332
309
 
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
310
  def initialize_generated_modules # :nodoc:
359
311
  generated_association_methods
360
312
  end
@@ -371,10 +323,10 @@ module ActiveRecord
371
323
 
372
324
  # Returns columns which shouldn't be exposed while calling +#inspect+.
373
325
  def filter_attributes
374
- if defined?(@filter_attributes)
375
- @filter_attributes
376
- else
326
+ if @filter_attributes.nil?
377
327
  superclass.filter_attributes
328
+ else
329
+ @filter_attributes
378
330
  end
379
331
  end
380
332
 
@@ -385,13 +337,13 @@ module ActiveRecord
385
337
  end
386
338
 
387
339
  def inspection_filter # :nodoc:
388
- if defined?(@filter_attributes)
340
+ if @filter_attributes.nil?
341
+ superclass.inspection_filter
342
+ else
389
343
  @inspection_filter ||= begin
390
344
  mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
391
345
  ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask)
392
346
  end
393
- else
394
- superclass.inspection_filter
395
347
  end
396
348
  end
397
349
 
@@ -402,7 +354,7 @@ module ActiveRecord
402
354
  elsif abstract_class?
403
355
  "#{super}(abstract)"
404
356
  elsif !connected?
405
- "#{super} (call '#{super}.connection' to establish a connection)"
357
+ "#{super} (call '#{super}.lease_connection' to establish a connection)"
406
358
  elsif table_exists?
407
359
  attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", "
408
360
  "#{super}(#{attr_list})"
@@ -411,12 +363,7 @@ module ActiveRecord
411
363
  end
412
364
  end
413
365
 
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.
366
+ # Returns an instance of +Arel::Table+ loaded with the current table name.
420
367
  def arel_table # :nodoc:
421
368
  @arel_table ||= Arel::Table.new(table_name, klass: self)
422
369
  end
@@ -429,12 +376,34 @@ module ActiveRecord
429
376
  TypeCaster::Map.new(self)
430
377
  end
431
378
 
432
- def cached_find_by_statement(key, &block) # :nodoc:
379
+ def cached_find_by_statement(connection, key, &block) # :nodoc:
433
380
  cache = @find_by_statement_cache[connection.prepared_statements]
434
381
  cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
435
382
  end
436
383
 
437
384
  private
385
+ def inherited(subclass)
386
+ super
387
+
388
+ # initialize cache at class definition for thread safety
389
+ subclass.initialize_find_by_cache
390
+ unless subclass.base_class?
391
+ klass = self
392
+ until klass.base_class?
393
+ klass.initialize_find_by_cache
394
+ klass = klass.superclass
395
+ end
396
+ end
397
+
398
+ subclass.class_eval do
399
+ @arel_table = nil
400
+ @predicate_builder = nil
401
+ @inspection_filter = nil
402
+ @filter_attributes ||= nil
403
+ @generated_association_methods ||= nil
404
+ end
405
+ end
406
+
438
407
  def relation
439
408
  relation = Relation.create(self)
440
409
 
@@ -448,6 +417,27 @@ module ActiveRecord
448
417
  def table_metadata
449
418
  TableMetadata.new(self, arel_table)
450
419
  end
420
+
421
+ def cached_find_by(keys, values)
422
+ with_connection do |connection|
423
+ statement = cached_find_by_statement(connection, keys) { |params|
424
+ wheres = keys.index_with do |key|
425
+ if key.is_a?(Array)
426
+ [key.map { params.bind }]
427
+ else
428
+ params.bind
429
+ end
430
+ end
431
+ where(wheres).limit(1)
432
+ }
433
+
434
+ begin
435
+ statement.execute(values.flatten, connection, allow_retry: true).first
436
+ rescue TypeError
437
+ raise ActiveRecord::StatementInvalid
438
+ end
439
+ end
440
+ end
451
441
  end
452
442
 
453
443
  # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
@@ -455,7 +445,7 @@ module ActiveRecord
455
445
  # In both instances, valid attribute keys are determined by the column names of the associated table --
456
446
  # hence you can't have attributes that aren't part of the table columns.
457
447
  #
458
- # ==== Example:
448
+ # ==== Example
459
449
  # # Instantiates a single new object
460
450
  # User.new(first_name: 'Jamie')
461
451
  def initialize(attributes = nil)
@@ -465,7 +455,7 @@ module ActiveRecord
465
455
  init_internals
466
456
  initialize_internals_callback
467
457
 
468
- assign_attributes(attributes) if attributes
458
+ super
469
459
 
470
460
  yield self if block_given?
471
461
  _run_initialize_callbacks
@@ -533,12 +523,17 @@ module ActiveRecord
533
523
  # only, not its associations. The extent of a "deep" copy is application
534
524
  # specific and is therefore left to the application to implement according
535
525
  # to its need.
536
- # The dup method does not preserve the timestamps (created|updated)_(at|on).
526
+ # The dup method does not preserve the timestamps (created|updated)_(at|on)
527
+ # and locking column.
537
528
 
538
529
  ##
539
530
  def initialize_dup(other) # :nodoc:
540
531
  @attributes = @attributes.deep_dup
541
- @attributes.reset(@primary_key)
532
+ if self.class.composite_primary_key?
533
+ @primary_key.each { |key| @attributes.reset(key) }
534
+ else
535
+ @attributes.reset(@primary_key)
536
+ end
542
537
 
543
538
  _run_initialize_callbacks
544
539
 
@@ -568,6 +563,35 @@ module ActiveRecord
568
563
  coder["active_record_yaml_version"] = 2
569
564
  end
570
565
 
566
+ ##
567
+ # :method: slice
568
+ #
569
+ # :call-seq: slice(*methods)
570
+ #
571
+ # Returns a hash of the given methods with their names as keys and returned
572
+ # values as values.
573
+ #
574
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
575
+ # topic.slice(:title, :author_name)
576
+ # => { "title" => "Budget", "author_name" => "Jason" }
577
+ #
578
+ #--
579
+ # Implemented by ActiveModel::Access#slice.
580
+
581
+ ##
582
+ # :method: values_at
583
+ #
584
+ # :call-seq: values_at(*methods)
585
+ #
586
+ # Returns an array of the values returned by the given methods.
587
+ #
588
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
589
+ # topic.values_at(:title, :author_name)
590
+ # => ["Budget", "Jason"]
591
+ #
592
+ #--
593
+ # Implemented by ActiveModel::Access#values_at.
594
+
571
595
  # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
572
596
  # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
573
597
  #
@@ -580,7 +604,7 @@ module ActiveRecord
580
604
  def ==(comparison_object)
581
605
  super ||
582
606
  comparison_object.instance_of?(self.class) &&
583
- !id.nil? &&
607
+ primary_key_values_present? &&
584
608
  comparison_object.id == id
585
609
  end
586
610
  alias :eql? :==
@@ -590,7 +614,7 @@ module ActiveRecord
590
614
  def hash
591
615
  id = self.id
592
616
 
593
- if id
617
+ if primary_key_values_present?
594
618
  self.class.hash ^ id.hash
595
619
  else
596
620
  super
@@ -642,25 +666,33 @@ module ActiveRecord
642
666
  #
643
667
  # user = User.first
644
668
  # user.strict_loading! # => true
645
- # user.comments
669
+ # user.address.city
670
+ # => ActiveRecord::StrictLoadingViolationError
671
+ # user.comments.to_a
646
672
  # => ActiveRecord::StrictLoadingViolationError
647
673
  #
648
- # === Parameters:
674
+ # ==== Parameters
649
675
  #
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.
676
+ # * +value+ - Boolean specifying whether to enable or disable strict loading.
677
+ # * <tt>:mode</tt> - Symbol specifying strict loading mode. Defaults to :all. Using
678
+ # :n_plus_one_only mode will only raise an error if an association that
679
+ # will lead to an n plus one query is lazily loaded.
654
680
  #
655
- # === Example:
681
+ # ==== Examples
656
682
  #
657
683
  # user = User.first
658
684
  # user.strict_loading!(false) # => false
659
- # user.comments
660
- # => #<ActiveRecord::Associations::CollectionProxy>
685
+ # user.address.city # => "Tatooine"
686
+ # user.comments.to_a # => [#<Comment:0x00...]
687
+ #
688
+ # user.strict_loading!(mode: :n_plus_one_only)
689
+ # user.address.city # => "Tatooine"
690
+ # user.comments.to_a # => [#<Comment:0x00...]
691
+ # user.comments.first.ratings.to_a
692
+ # => ActiveRecord::StrictLoadingViolationError
661
693
  def strict_loading!(value = true, mode: :all)
662
694
  unless [:all, :n_plus_one_only].include?(mode)
663
- raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only]."
695
+ raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
664
696
  end
665
697
 
666
698
  @strict_loading_mode = mode
@@ -674,7 +706,16 @@ module ActiveRecord
674
706
  @strict_loading_mode == :n_plus_one_only
675
707
  end
676
708
 
709
+ # Returns +true+ if the record uses strict_loading with +:all+ mode enabled.
710
+ def strict_loading_all?
711
+ @strict_loading_mode == :all
712
+ end
713
+
677
714
  # Marks this record as read only.
715
+ #
716
+ # customer = Customer.first
717
+ # customer.readonly!
718
+ # customer.save # Raises an ActiveRecord::ReadOnlyRecord
678
719
  def readonly!
679
720
  @readonly = true
680
721
  end
@@ -683,21 +724,14 @@ module ActiveRecord
683
724
  self.class.connection_handler
684
725
  end
685
726
 
686
- # Returns the contents of the record as a nicely formatted string.
727
+ # Returns the attributes specified by <tt>.attributes_for_inspect</tt> as a nicely formatted string.
687
728
  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
729
+ inspect_with_attributes(attributes_for_inspect)
730
+ end
699
731
 
700
- "#<#{self.class} #{inspection}>"
732
+ # Returns the full contents of the record as a nicely formatted string.
733
+ def full_inspect
734
+ inspect_with_attributes(attribute_names)
701
735
  end
702
736
 
703
737
  # Takes a PP and prettily prints this record to it, allowing you to get a nice result from <tt>pp record</tt>
@@ -705,17 +739,17 @@ module ActiveRecord
705
739
  def pretty_print(pp)
706
740
  return super if custom_inspect_method_defined?
707
741
  pp.object_address_group(self) do
708
- if defined?(@attributes) && @attributes
709
- attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
742
+ if @attributes
743
+ attr_names = attributes_for_inspect.select { |name| _has_attribute?(name.to_s) }
710
744
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
745
+ attr_name = attr_name.to_s
711
746
  pp.breakable " "
712
747
  pp.group(1) do
713
748
  pp.text attr_name
714
749
  pp.text ":"
715
750
  pp.breakable
716
- value = _read_attribute(attr_name)
717
- value = inspection_filter.filter_param(attr_name, value) unless value.nil?
718
- pp.pp value
751
+ value = attribute_for_inspect(attr_name)
752
+ pp.text value
719
753
  end
720
754
  end
721
755
  else
@@ -725,16 +759,6 @@ module ActiveRecord
725
759
  end
726
760
  end
727
761
 
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
762
  private
739
763
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
740
764
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
@@ -782,5 +806,24 @@ module ActiveRecord
782
806
  def inspection_filter
783
807
  self.class.inspection_filter
784
808
  end
809
+
810
+ def inspect_with_attributes(attributes_to_list)
811
+ inspection = if @attributes
812
+ attributes_to_list.filter_map do |name|
813
+ name = name.to_s
814
+ if _has_attribute?(name)
815
+ "#{name}: #{attribute_for_inspect(name)}"
816
+ end
817
+ end.join(", ")
818
+ else
819
+ "not initialized"
820
+ end
821
+
822
+ "#<#{self.class} #{inspection}>"
823
+ end
824
+
825
+ def attributes_for_inspect
826
+ self.class.attributes_for_inspect == :all ? attribute_names : self.class.attributes_for_inspect
827
+ end
785
828
  end
786
829
  end