activerecord 7.0.5 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (234) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1624 -1338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +16 -10
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -7
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +31 -7
  22. data/lib/active_record/associations/preloader.rb +13 -10
  23. data/lib/active_record/associations/singular_association.rb +6 -8
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +313 -217
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +74 -51
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +290 -125
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +505 -102
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +214 -113
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +18 -13
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -41
  71. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  73. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  74. data/lib/active_record/connection_adapters/postgresql/quoting.rb +15 -8
  75. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  76. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  77. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  78. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  79. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +361 -60
  80. data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
  81. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  82. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  83. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  84. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  85. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  86. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  87. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +211 -83
  88. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  89. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  90. data/lib/active_record/connection_adapters/trilogy_adapter.rb +262 -0
  91. data/lib/active_record/connection_adapters.rb +3 -1
  92. data/lib/active_record/connection_handling.rb +72 -95
  93. data/lib/active_record/core.rb +175 -153
  94. data/lib/active_record/counter_cache.rb +46 -25
  95. data/lib/active_record/database_configurations/database_config.rb +9 -3
  96. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  97. data/lib/active_record/database_configurations/url_config.rb +17 -11
  98. data/lib/active_record/database_configurations.rb +86 -33
  99. data/lib/active_record/delegated_type.rb +9 -4
  100. data/lib/active_record/deprecator.rb +7 -0
  101. data/lib/active_record/destroy_association_async_job.rb +2 -0
  102. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  103. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  104. data/lib/active_record/encryption/config.rb +25 -1
  105. data/lib/active_record/encryption/configurable.rb +12 -19
  106. data/lib/active_record/encryption/context.rb +10 -3
  107. data/lib/active_record/encryption/contexts.rb +5 -1
  108. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  109. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +21 -6
  111. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  112. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  113. data/lib/active_record/encryption/key_generator.rb +12 -1
  114. data/lib/active_record/encryption/message_serializer.rb +2 -0
  115. data/lib/active_record/encryption/properties.rb +3 -3
  116. data/lib/active_record/encryption/scheme.rb +19 -22
  117. data/lib/active_record/encryption.rb +1 -0
  118. data/lib/active_record/enum.rb +112 -28
  119. data/lib/active_record/errors.rb +112 -18
  120. data/lib/active_record/explain.rb +23 -3
  121. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  122. data/lib/active_record/fixture_set/render_context.rb +2 -0
  123. data/lib/active_record/fixture_set/table_row.rb +29 -8
  124. data/lib/active_record/fixtures.rb +135 -71
  125. data/lib/active_record/future_result.rb +31 -5
  126. data/lib/active_record/gem_version.rb +4 -4
  127. data/lib/active_record/inheritance.rb +30 -16
  128. data/lib/active_record/insert_all.rb +57 -10
  129. data/lib/active_record/integration.rb +8 -8
  130. data/lib/active_record/internal_metadata.rb +120 -30
  131. data/lib/active_record/locking/optimistic.rb +32 -18
  132. data/lib/active_record/locking/pessimistic.rb +5 -2
  133. data/lib/active_record/log_subscriber.rb +29 -12
  134. data/lib/active_record/marshalling.rb +56 -0
  135. data/lib/active_record/message_pack.rb +124 -0
  136. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  137. data/lib/active_record/middleware/database_selector.rb +6 -8
  138. data/lib/active_record/middleware/shard_selector.rb +3 -1
  139. data/lib/active_record/migration/command_recorder.rb +104 -5
  140. data/lib/active_record/migration/compatibility.rb +150 -58
  141. data/lib/active_record/migration/default_strategy.rb +23 -0
  142. data/lib/active_record/migration/execution_strategy.rb +19 -0
  143. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  144. data/lib/active_record/migration.rb +271 -114
  145. data/lib/active_record/model_schema.rb +64 -44
  146. data/lib/active_record/nested_attributes.rb +24 -6
  147. data/lib/active_record/normalization.rb +167 -0
  148. data/lib/active_record/persistence.rb +195 -42
  149. data/lib/active_record/promise.rb +84 -0
  150. data/lib/active_record/query_cache.rb +3 -21
  151. data/lib/active_record/query_logs.rb +77 -52
  152. data/lib/active_record/query_logs_formatter.rb +41 -0
  153. data/lib/active_record/querying.rb +15 -2
  154. data/lib/active_record/railtie.rb +109 -47
  155. data/lib/active_record/railties/controller_runtime.rb +14 -9
  156. data/lib/active_record/railties/databases.rake +142 -148
  157. data/lib/active_record/railties/job_runtime.rb +23 -0
  158. data/lib/active_record/readonly_attributes.rb +32 -5
  159. data/lib/active_record/reflection.rb +182 -44
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  161. data/lib/active_record/relation/batches.rb +190 -61
  162. data/lib/active_record/relation/calculations.rb +187 -63
  163. data/lib/active_record/relation/delegation.rb +23 -9
  164. data/lib/active_record/relation/finder_methods.rb +77 -16
  165. data/lib/active_record/relation/merger.rb +2 -0
  166. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  167. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  168. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  169. data/lib/active_record/relation/predicate_builder.rb +27 -16
  170. data/lib/active_record/relation/query_attribute.rb +25 -1
  171. data/lib/active_record/relation/query_methods.rb +386 -70
  172. data/lib/active_record/relation/spawn_methods.rb +18 -1
  173. data/lib/active_record/relation.rb +91 -35
  174. data/lib/active_record/result.rb +25 -9
  175. data/lib/active_record/runtime_registry.rb +24 -1
  176. data/lib/active_record/sanitization.rb +51 -11
  177. data/lib/active_record/schema.rb +2 -3
  178. data/lib/active_record/schema_dumper.rb +46 -7
  179. data/lib/active_record/schema_migration.rb +68 -33
  180. data/lib/active_record/scoping/default.rb +15 -5
  181. data/lib/active_record/scoping/named.rb +2 -2
  182. data/lib/active_record/scoping.rb +2 -1
  183. data/lib/active_record/secure_password.rb +60 -0
  184. data/lib/active_record/secure_token.rb +21 -3
  185. data/lib/active_record/signed_id.rb +7 -5
  186. data/lib/active_record/store.rb +8 -8
  187. data/lib/active_record/suppressor.rb +3 -1
  188. data/lib/active_record/table_metadata.rb +16 -3
  189. data/lib/active_record/tasks/database_tasks.rb +127 -105
  190. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  191. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  192. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  193. data/lib/active_record/test_fixtures.rb +113 -96
  194. data/lib/active_record/timestamp.rb +27 -15
  195. data/lib/active_record/token_for.rb +113 -0
  196. data/lib/active_record/touch_later.rb +11 -6
  197. data/lib/active_record/transactions.rb +39 -13
  198. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  199. data/lib/active_record/type/internal/timezone.rb +7 -2
  200. data/lib/active_record/type/serialized.rb +4 -0
  201. data/lib/active_record/type/time.rb +4 -0
  202. data/lib/active_record/validations/absence.rb +1 -1
  203. data/lib/active_record/validations/numericality.rb +5 -4
  204. data/lib/active_record/validations/presence.rb +5 -28
  205. data/lib/active_record/validations/uniqueness.rb +47 -2
  206. data/lib/active_record/validations.rb +8 -4
  207. data/lib/active_record/version.rb +1 -1
  208. data/lib/active_record.rb +121 -16
  209. data/lib/arel/errors.rb +10 -0
  210. data/lib/arel/factory_methods.rb +4 -0
  211. data/lib/arel/nodes/and.rb +4 -0
  212. data/lib/arel/nodes/binary.rb +6 -1
  213. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  214. data/lib/arel/nodes/cte.rb +36 -0
  215. data/lib/arel/nodes/fragments.rb +35 -0
  216. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  217. data/lib/arel/nodes/leading_join.rb +8 -0
  218. data/lib/arel/nodes/node.rb +111 -2
  219. data/lib/arel/nodes/sql_literal.rb +6 -0
  220. data/lib/arel/nodes/table_alias.rb +4 -0
  221. data/lib/arel/nodes.rb +4 -0
  222. data/lib/arel/predications.rb +2 -0
  223. data/lib/arel/table.rb +9 -5
  224. data/lib/arel/visitors/mysql.rb +8 -1
  225. data/lib/arel/visitors/to_sql.rb +81 -17
  226. data/lib/arel/visitors/visitor.rb +2 -2
  227. data/lib/arel.rb +16 -2
  228. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  229. data/lib/rails/generators/active_record/migration.rb +3 -1
  230. data/lib/rails/generators/active_record/model/USAGE +113 -0
  231. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  232. metadata +51 -15
  233. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  234. 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,19 +54,19 @@ 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)
@@ -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
@@ -99,33 +122,6 @@ module ActiveRecord
99
122
  ActiveSupport::IsolatedExecutionState[:active_record_connection_handler] = handler
100
123
  end
101
124
 
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
125
  def self.asynchronous_queries_session # :nodoc:
130
126
  asynchronous_queries_tracker.current_session
131
127
  end
@@ -145,16 +141,12 @@ module ActiveRecord
145
141
  # ActiveRecord::Base.current_role #=> :reading
146
142
  # end
147
143
  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
144
+ connected_to_stack.reverse_each do |hash|
145
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
146
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_class_for_self)
157
147
  end
148
+
149
+ default_role
158
150
  end
159
151
 
160
152
  # Returns the symbol representing the current connected shard.
@@ -186,16 +178,12 @@ module ActiveRecord
186
178
  # ActiveRecord::Base.current_preventing_writes #=> false
187
179
  # end
188
180
  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
181
+ connected_to_stack.reverse_each do |hash|
182
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
183
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_class_for_self)
198
184
  end
185
+
186
+ false
199
187
  end
200
188
 
201
189
  def self.connected_to_stack # :nodoc:
@@ -252,19 +240,6 @@ module ActiveRecord
252
240
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
253
241
  end
254
242
 
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
243
  def find(*ids) # :nodoc:
269
244
  # We don't have cache keys for this stuff yet
270
245
  return super unless ids.length == 1
@@ -274,14 +249,8 @@ module ActiveRecord
274
249
 
275
250
  return super if StatementCache.unsupported_value?(id)
276
251
 
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))
252
+ cached_find_by([primary_key], [id]) ||
253
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
285
254
  end
286
255
 
287
256
  def find_by(*args) # :nodoc:
@@ -303,58 +272,38 @@ module ActiveRecord
303
272
  elsif reflection.belongs_to? && !reflection.polymorphic?
304
273
  key = reflection.join_foreign_key
305
274
  pkey = reflection.join_primary_key
306
- value = value.public_send(pkey) if value.respond_to?(pkey)
275
+
276
+ if pkey.is_a?(Array)
277
+ if pkey.all? { |attribute| value.respond_to?(attribute) }
278
+ value = pkey.map do |attribute|
279
+ if attribute == "id"
280
+ value.id_value
281
+ else
282
+ value.public_send(attribute)
283
+ end
284
+ end
285
+ composite_primary_key = true
286
+ end
287
+ else
288
+ value = value.public_send(pkey) if value.respond_to?(pkey)
289
+ end
307
290
  end
308
291
 
309
- if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
292
+ if !composite_primary_key &&
293
+ (!columns_hash.key?(key) || StatementCache.unsupported_value?(value))
310
294
  return super
311
295
  end
312
296
 
313
297
  h[key] = value
314
298
  end
315
299
 
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
300
+ cached_find_by(hash.keys, hash.values)
327
301
  end
328
302
 
329
303
  def find_by!(*args) # :nodoc:
330
304
  find_by(*args) || where(*args).raise_record_not_found_exception!
331
305
  end
332
306
 
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
307
  def initialize_generated_modules # :nodoc:
359
308
  generated_association_methods
360
309
  end
@@ -371,10 +320,10 @@ module ActiveRecord
371
320
 
372
321
  # Returns columns which shouldn't be exposed while calling +#inspect+.
373
322
  def filter_attributes
374
- if defined?(@filter_attributes)
375
- @filter_attributes
376
- else
323
+ if @filter_attributes.nil?
377
324
  superclass.filter_attributes
325
+ else
326
+ @filter_attributes
378
327
  end
379
328
  end
380
329
 
@@ -385,13 +334,13 @@ module ActiveRecord
385
334
  end
386
335
 
387
336
  def inspection_filter # :nodoc:
388
- if defined?(@filter_attributes)
337
+ if @filter_attributes.nil?
338
+ superclass.inspection_filter
339
+ else
389
340
  @inspection_filter ||= begin
390
341
  mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
391
342
  ActiveSupport::ParameterFilter.new(@filter_attributes, mask: mask)
392
343
  end
393
- else
394
- superclass.inspection_filter
395
344
  end
396
345
  end
397
346
 
@@ -411,12 +360,7 @@ module ActiveRecord
411
360
  end
412
361
  end
413
362
 
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.
363
+ # Returns an instance of +Arel::Table+ loaded with the current table name.
420
364
  def arel_table # :nodoc:
421
365
  @arel_table ||= Arel::Table.new(table_name, klass: self)
422
366
  end
@@ -435,6 +379,28 @@ module ActiveRecord
435
379
  end
436
380
 
437
381
  private
382
+ def inherited(subclass)
383
+ super
384
+
385
+ # initialize cache at class definition for thread safety
386
+ subclass.initialize_find_by_cache
387
+ unless subclass.base_class?
388
+ klass = self
389
+ until klass.base_class?
390
+ klass.initialize_find_by_cache
391
+ klass = klass.superclass
392
+ end
393
+ end
394
+
395
+ subclass.class_eval do
396
+ @arel_table = nil
397
+ @predicate_builder = nil
398
+ @inspection_filter = nil
399
+ @filter_attributes = nil
400
+ @generated_association_methods = nil
401
+ end
402
+ end
403
+
438
404
  def relation
439
405
  relation = Relation.create(self)
440
406
 
@@ -448,6 +414,25 @@ module ActiveRecord
448
414
  def table_metadata
449
415
  TableMetadata.new(self, arel_table)
450
416
  end
417
+
418
+ def cached_find_by(keys, values)
419
+ statement = cached_find_by_statement(keys) { |params|
420
+ wheres = keys.index_with do |key|
421
+ if key.is_a?(Array)
422
+ [key.map { params.bind }]
423
+ else
424
+ params.bind
425
+ end
426
+ end
427
+ where(wheres).limit(1)
428
+ }
429
+
430
+ begin
431
+ statement.execute(values.flatten, connection).first
432
+ rescue TypeError
433
+ raise ActiveRecord::StatementInvalid
434
+ end
435
+ end
451
436
  end
452
437
 
453
438
  # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
@@ -455,7 +440,7 @@ module ActiveRecord
455
440
  # In both instances, valid attribute keys are determined by the column names of the associated table --
456
441
  # hence you can't have attributes that aren't part of the table columns.
457
442
  #
458
- # ==== Example:
443
+ # ==== Example
459
444
  # # Instantiates a single new object
460
445
  # User.new(first_name: 'Jamie')
461
446
  def initialize(attributes = nil)
@@ -533,12 +518,17 @@ module ActiveRecord
533
518
  # only, not its associations. The extent of a "deep" copy is application
534
519
  # specific and is therefore left to the application to implement according
535
520
  # to its need.
536
- # The dup method does not preserve the timestamps (created|updated)_(at|on).
521
+ # The dup method does not preserve the timestamps (created|updated)_(at|on)
522
+ # and locking column.
537
523
 
538
524
  ##
539
525
  def initialize_dup(other) # :nodoc:
540
526
  @attributes = @attributes.deep_dup
541
- @attributes.reset(@primary_key)
527
+ if self.class.composite_primary_key?
528
+ @primary_key.each { |key| @attributes.reset(key) }
529
+ else
530
+ @attributes.reset(@primary_key)
531
+ end
542
532
 
543
533
  _run_initialize_callbacks
544
534
 
@@ -568,6 +558,35 @@ module ActiveRecord
568
558
  coder["active_record_yaml_version"] = 2
569
559
  end
570
560
 
561
+ ##
562
+ # :method: slice
563
+ #
564
+ # :call-seq: slice(*methods)
565
+ #
566
+ # Returns a hash of the given methods with their names as keys and returned
567
+ # values as values.
568
+ #
569
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
570
+ # topic.slice(:title, :author_name)
571
+ # => { "title" => "Budget", "author_name" => "Jason" }
572
+ #
573
+ #--
574
+ # Implemented by ActiveModel::Access#slice.
575
+
576
+ ##
577
+ # :method: values_at
578
+ #
579
+ # :call-seq: values_at(*methods)
580
+ #
581
+ # Returns an array of the values returned by the given methods.
582
+ #
583
+ # topic = Topic.new(title: "Budget", author_name: "Jason")
584
+ # topic.values_at(:title, :author_name)
585
+ # => ["Budget", "Jason"]
586
+ #
587
+ #--
588
+ # Implemented by ActiveModel::Access#values_at.
589
+
571
590
  # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
572
591
  # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
573
592
  #
@@ -580,7 +599,7 @@ module ActiveRecord
580
599
  def ==(comparison_object)
581
600
  super ||
582
601
  comparison_object.instance_of?(self.class) &&
583
- !id.nil? &&
602
+ primary_key_values_present? &&
584
603
  comparison_object.id == id
585
604
  end
586
605
  alias :eql? :==
@@ -590,7 +609,7 @@ module ActiveRecord
590
609
  def hash
591
610
  id = self.id
592
611
 
593
- if id
612
+ if primary_key_values_present?
594
613
  self.class.hash ^ id.hash
595
614
  else
596
615
  super
@@ -642,25 +661,33 @@ module ActiveRecord
642
661
  #
643
662
  # user = User.first
644
663
  # user.strict_loading! # => true
645
- # user.comments
664
+ # user.address.city
665
+ # => ActiveRecord::StrictLoadingViolationError
666
+ # user.comments.to_a
646
667
  # => ActiveRecord::StrictLoadingViolationError
647
668
  #
648
- # === Parameters:
669
+ # ==== Parameters
649
670
  #
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.
671
+ # * +value+ - Boolean specifying whether to enable or disable strict loading.
672
+ # * <tt>:mode</tt> - Symbol specifying strict loading mode. Defaults to :all. Using
673
+ # :n_plus_one_only mode will only raise an error if an association that
674
+ # will lead to an n plus one query is lazily loaded.
654
675
  #
655
- # === Example:
676
+ # ==== Examples
656
677
  #
657
678
  # user = User.first
658
679
  # user.strict_loading!(false) # => false
659
- # user.comments
660
- # => #<ActiveRecord::Associations::CollectionProxy>
680
+ # user.address.city # => "Tatooine"
681
+ # user.comments.to_a # => [#<Comment:0x00...]
682
+ #
683
+ # user.strict_loading!(mode: :n_plus_one_only)
684
+ # user.address.city # => "Tatooine"
685
+ # user.comments.to_a # => [#<Comment:0x00...]
686
+ # user.comments.first.ratings.to_a
687
+ # => ActiveRecord::StrictLoadingViolationError
661
688
  def strict_loading!(value = true, mode: :all)
662
689
  unless [:all, :n_plus_one_only].include?(mode)
663
- raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only]."
690
+ raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
664
691
  end
665
692
 
666
693
  @strict_loading_mode = mode
@@ -675,6 +702,10 @@ module ActiveRecord
675
702
  end
676
703
 
677
704
  # Marks this record as read only.
705
+ #
706
+ # customer = Customer.first
707
+ # customer.readonly!
708
+ # customer.save # Raises an ActiveRecord::ReadOnlyRecord
678
709
  def readonly!
679
710
  @readonly = true
680
711
  end
@@ -688,7 +719,7 @@ module ActiveRecord
688
719
  # We check defined?(@attributes) not to issue warnings if the object is
689
720
  # allocated but not initialized.
690
721
  inspection = if defined?(@attributes) && @attributes
691
- self.class.attribute_names.filter_map do |name|
722
+ attribute_names.filter_map do |name|
692
723
  if _has_attribute?(name)
693
724
  "#{name}: #{attribute_for_inspect(name)}"
694
725
  end
@@ -725,16 +756,6 @@ module ActiveRecord
725
756
  end
726
757
  end
727
758
 
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
759
  private
739
760
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
740
761
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
@@ -763,6 +784,7 @@ module ActiveRecord
763
784
  @strict_loading_mode = :all
764
785
 
765
786
  klass.define_attribute_methods
787
+ klass.generate_alias_attributes
766
788
  end
767
789
 
768
790
  def initialize_internals_callback
@@ -5,6 +5,10 @@ module ActiveRecord
5
5
  module CounterCache
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ included do
9
+ class_attribute :_counter_cache_columns, instance_accessor: false, default: []
10
+ end
11
+
8
12
  module ClassMethods
9
13
  # Resets one or more counter caches to their correct value using an SQL
10
14
  # count query. This is useful when adding new counter caches, or if the
@@ -29,6 +33,7 @@ module ActiveRecord
29
33
  def reset_counters(id, *counters, touch: nil)
30
34
  object = find(id)
31
35
 
36
+ updates = {}
32
37
  counters.each do |counter_association|
33
38
  has_many_association = _reflect_on_association(counter_association)
34
39
  unless has_many_association
@@ -47,19 +52,21 @@ module ActiveRecord
47
52
  reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
48
53
  counter_name = reflection.counter_cache_column
49
54
 
50
- updates = { counter_name => object.send(counter_association).count(:all) }
51
-
52
- if touch
53
- names = touch if touch != true
54
- names = Array.wrap(names)
55
- options = names.extract_options!
56
- touch_updates = touch_attributes_with_time(*names, **options)
57
- updates.merge!(touch_updates)
58
- end
55
+ count_was = object.send(counter_name)
56
+ count = object.send(counter_association).count(:all)
57
+ updates[counter_name] = count if count != count_was
58
+ end
59
59
 
60
- unscoped.where(primary_key => object.id).update_all(updates)
60
+ if touch
61
+ names = touch if touch != true
62
+ names = Array.wrap(names)
63
+ options = names.extract_options!
64
+ touch_updates = touch_attributes_with_time(*names, **options)
65
+ updates.merge!(touch_updates)
61
66
  end
62
67
 
68
+ unscoped.where(primary_key => object.id).update_all(updates) if updates.any?
69
+
63
70
  true
64
71
  end
65
72
 
@@ -80,28 +87,28 @@ module ActiveRecord
80
87
  #
81
88
  # ==== Examples
82
89
  #
83
- # # For the Post with id of 5, decrement the comment_count by 1, and
84
- # # increment the action_count by 1
85
- # Post.update_counters 5, comment_count: -1, action_count: 1
90
+ # # For the Post with id of 5, decrement the comments_count by 1, and
91
+ # # increment the actions_count by 1
92
+ # Post.update_counters 5, comments_count: -1, actions_count: 1
86
93
  # # Executes the following SQL:
87
94
  # # UPDATE posts
88
- # # SET comment_count = COALESCE(comment_count, 0) - 1,
89
- # # action_count = COALESCE(action_count, 0) + 1
95
+ # # SET comments_count = COALESCE(comments_count, 0) - 1,
96
+ # # actions_count = COALESCE(actions_count, 0) + 1
90
97
  # # WHERE id = 5
91
98
  #
92
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
93
- # Post.update_counters [10, 15], comment_count: 1
99
+ # # For the Posts with id of 10 and 15, increment the comments_count by 1
100
+ # Post.update_counters [10, 15], comments_count: 1
94
101
  # # Executes the following SQL:
95
102
  # # UPDATE posts
96
- # # SET comment_count = COALESCE(comment_count, 0) + 1
103
+ # # SET comments_count = COALESCE(comments_count, 0) + 1
97
104
  # # WHERE id IN (10, 15)
98
105
  #
99
- # # For the Posts with id of 10 and 15, increment the comment_count by 1
106
+ # # For the Posts with id of 10 and 15, increment the comments_count by 1
100
107
  # # and update the updated_at value for each counter.
101
- # Post.update_counters [10, 15], comment_count: 1, touch: true
108
+ # Post.update_counters [10, 15], comments_count: 1, touch: true
102
109
  # # Executes the following SQL:
103
110
  # # UPDATE posts
104
- # # SET comment_count = COALESCE(comment_count, 0) + 1,
111
+ # # SET comments_count = COALESCE(comments_count, 0) + 1,
105
112
  # # `updated_at` = '2016-10-13T09:59:23-05:00'
106
113
  # # WHERE id IN (10, 15)
107
114
  def update_counters(id, counters)
@@ -119,6 +126,7 @@ module ActiveRecord
119
126
  #
120
127
  # * +counter_name+ - The name of the field that should be incremented.
121
128
  # * +id+ - The id of the object that should be incremented or an array of ids.
129
+ # * <tt>:by</tt> - The amount by which to increment the value. Defaults to +1+.
122
130
  # * <tt>:touch</tt> - Touch timestamp columns when updating.
123
131
  # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
124
132
  # touch that column or an array of symbols to touch just those ones.
@@ -129,10 +137,14 @@ module ActiveRecord
129
137
  # DiscussionBoard.increment_counter(:posts_count, 5)
130
138
  #
131
139
  # # Increment the posts_count column for the record with an id of 5
140
+ # # by a specific amount.
141
+ # DiscussionBoard.increment_counter(:posts_count, 5, by: 3)
142
+ #
143
+ # # Increment the posts_count column for the record with an id of 5
132
144
  # # and update the updated_at value.
133
145
  # DiscussionBoard.increment_counter(:posts_count, 5, touch: true)
134
- def increment_counter(counter_name, id, touch: nil)
135
- update_counters(id, counter_name => 1, touch: touch)
146
+ def increment_counter(counter_name, id, by: 1, touch: nil)
147
+ update_counters(id, counter_name => by, touch: touch)
136
148
  end
137
149
 
138
150
  # Decrement a numeric field by one, via a direct SQL update.
@@ -144,6 +156,7 @@ module ActiveRecord
144
156
  #
145
157
  # * +counter_name+ - The name of the field that should be decremented.
146
158
  # * +id+ - The id of the object that should be decremented or an array of ids.
159
+ # * <tt>:by</tt> - The amount by which to decrement the value. Defaults to +1+.
147
160
  # * <tt>:touch</tt> - Touch timestamp columns when updating.
148
161
  # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to
149
162
  # touch that column or an array of symbols to touch just those ones.
@@ -154,10 +167,18 @@ module ActiveRecord
154
167
  # DiscussionBoard.decrement_counter(:posts_count, 5)
155
168
  #
156
169
  # # Decrement the posts_count column for the record with an id of 5
170
+ # by a specific amount.
171
+ # DiscussionBoard.decrement_counter(:posts_count, 5, by: 3)
172
+ #
173
+ # # Decrement the posts_count column for the record with an id of 5
157
174
  # # and update the updated_at value.
158
175
  # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true)
159
- def decrement_counter(counter_name, id, touch: nil)
160
- update_counters(id, counter_name => -1, touch: touch)
176
+ def decrement_counter(counter_name, id, by: 1, touch: nil)
177
+ update_counters(id, counter_name => -by, touch: touch)
178
+ end
179
+
180
+ def counter_cache_column?(name) # :nodoc:
181
+ _counter_cache_columns.include?(name)
161
182
  end
162
183
  end
163
184