activerecord 7.2.2.1 → 8.1.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -11,12 +11,18 @@ module ActiveRecord
11
11
  # # Check for any number of queries
12
12
  # assert_queries_count { Post.first }
13
13
  #
14
- # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
14
+ # Any unmaterialized transactions will be materialized to ensure only
15
+ # queries attempted intside the block are counted.
16
+ #
17
+ # If the +:include_schema+ option is provided, any queries (including
18
+ # schema related) are counted. Setting this option also skips leasing a
19
+ # connection to materialize pending transactions since we want to count
20
+ # queries executed at connection open (e.g., type map).
15
21
  #
16
22
  # assert_queries_count(1, include_schema: true) { Post.columns }
17
23
  #
18
24
  def assert_queries_count(count = nil, include_schema: false, &block)
19
- ActiveRecord::Base.lease_connection.materialize_transactions
25
+ ActiveRecord::Base.lease_connection.materialize_transactions unless include_schema
20
26
 
21
27
  counter = SQLCounter.new
22
28
  ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
@@ -169,8 +169,10 @@ module ActiveRecord
169
169
  # Clear attributes and changed_attributes
170
170
  def clear_timestamp_attributes
171
171
  all_timestamp_attributes_in_model.each do |attribute_name|
172
- self[attribute_name] = nil
173
- clear_attribute_change(attribute_name)
172
+ if self[attribute_name]
173
+ self[attribute_name] = nil
174
+ clear_attribute_change(attribute_name)
175
+ end
174
176
  end
175
177
  end
176
178
  end
@@ -40,7 +40,7 @@ module ActiveRecord
40
40
  # +nil+ if the token is invalid or the record was not found.
41
41
  def find_by_token_for(purpose, token)
42
42
  raise UnknownPrimaryKey.new(self) unless model.primary_key
43
- model.token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(model.primary_key => id) }
43
+ model.token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(model.primary_key => [id]) }
44
44
  end
45
45
 
46
46
  # Finds a record using a given +token+ for a predefined +purpose+. Raises
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  # # We are inside a real and not finalized transaction.
27
27
  # end
28
28
  #
29
- # Closed transactions are `blank?` too.
29
+ # Closed transactions are +blank?+ too.
30
30
  #
31
31
  # == Callbacks
32
32
  #
@@ -101,9 +101,6 @@ module ActiveRecord
101
101
  #
102
102
  # If the entire chain of nested transactions are all successfully committed,
103
103
  # the block is never called.
104
- #
105
- # If the transaction is already finalized, attempting to register a callback
106
- # will raise ActiveRecord::ActiveRecordError.
107
104
  def after_rollback(&block)
108
105
  @internal_transaction&.after_rollback(&block)
109
106
  end
@@ -115,7 +112,7 @@ module ActiveRecord
115
112
 
116
113
  # Returns true if the transaction doesn't exist or is finalized.
117
114
  def closed?
118
- @internal_transaction.nil? || @internal_transaction.state.finalized?
115
+ @internal_transaction.nil? || @internal_transaction.closed?
119
116
  end
120
117
 
121
118
  alias_method :blank?, :closed?
@@ -13,7 +13,7 @@ module ActiveRecord
13
13
  scope: [:kind, :name]
14
14
  end
15
15
 
16
- attr_accessor :_new_record_before_last_commit # :nodoc:
16
+ attr_accessor :_new_record_before_last_commit, :_last_transaction_return_status # :nodoc:
17
17
 
18
18
  # = Active Record \Transactions
19
19
  #
@@ -219,22 +219,41 @@ module ActiveRecord
219
219
  # database error will occur because the savepoint has already been
220
220
  # automatically released. The following example demonstrates the problem:
221
221
  #
222
- # Model.lease_connection.transaction do # BEGIN
223
- # Model.lease_connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
224
- # Model.lease_connection.create_table(...) # active_record_1 now automatically released
225
- # end # RELEASE SAVEPOINT active_record_1
226
- # # ^^^^ BOOM! database error!
227
- # end
222
+ # Model.transaction do # BEGIN
223
+ # Model.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
224
+ # Model.lease_connection.create_table(...) # active_record_1 now automatically released
225
+ # end # RELEASE SAVEPOINT active_record_1
226
+ # end # ^^^^ BOOM! database error!
228
227
  #
229
228
  # Note that "TRUNCATE" is also a MySQL DDL statement!
230
229
  module ClassMethods
231
230
  # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
232
231
  def transaction(**options, &block)
233
232
  with_connection do |connection|
234
- connection.transaction(**options, &block)
233
+ connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?) do
234
+ connection.transaction(**options, &block)
235
+ end
235
236
  end
236
237
  end
237
238
 
239
+ # Makes all transactions the current pool use the isolation level initiated within the block.
240
+ def with_pool_transaction_isolation_level(isolation_level, &block)
241
+ if current_transaction.open?
242
+ raise ActiveRecord::TransactionIsolationError, "cannot set default isolation level while transaction is open"
243
+ end
244
+
245
+ old_level = connection_pool.pool_transaction_isolation_level
246
+ connection_pool.pool_transaction_isolation_level = isolation_level
247
+ yield
248
+ ensure
249
+ connection_pool.pool_transaction_isolation_level = old_level
250
+ end
251
+
252
+ # Returns the default isolation level for the connection pool, set earlier by #with_pool_transaction_isolation_level.
253
+ def pool_transaction_isolation_level
254
+ connection_pool.pool_transaction_isolation_level
255
+ end
256
+
238
257
  # Returns a representation of the current transaction state,
239
258
  # which can be a top level transaction, a savepoint, or the absence of a transaction.
240
259
  #
@@ -408,17 +427,20 @@ module ActiveRecord
408
427
  # instance.
409
428
  def with_transaction_returning_status
410
429
  self.class.with_connection do |connection|
411
- status = nil
412
- ensure_finalize = !connection.transaction_open?
430
+ connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?) do
431
+ status = nil
432
+ ensure_finalize = !connection.transaction_open?
413
433
 
414
- connection.transaction do
415
- add_to_transaction(ensure_finalize || has_transactional_callbacks?)
416
- remember_transaction_record_state
434
+ connection.transaction do
435
+ add_to_transaction(ensure_finalize || has_transactional_callbacks?)
436
+ remember_transaction_record_state
417
437
 
418
- status = yield
419
- raise ActiveRecord::Rollback unless status
438
+ status = yield
439
+ raise ActiveRecord::Rollback unless status
440
+ end
441
+ @_last_transaction_return_status = status
442
+ status
420
443
  end
421
- status
422
444
  end
423
445
  end
424
446
 
@@ -433,6 +455,7 @@ module ActiveRecord
433
455
  def init_internals
434
456
  super
435
457
  @_start_transaction_state = nil
458
+ @_last_transaction_return_status = nil
436
459
  @_committed_already_called = nil
437
460
  @_new_record_before_last_commit = nil
438
461
  end
@@ -26,6 +26,7 @@ module ActiveRecord
26
26
  if block
27
27
  @mapping[key] = block
28
28
  else
29
+ value.freeze
29
30
  @mapping[key] = proc { value }
30
31
  end
31
32
  @cache.clear
@@ -50,7 +51,7 @@ module ActiveRecord
50
51
 
51
52
  private
52
53
  def perform_fetch(type, *args, &block)
53
- @mapping.fetch(type, block).call(type, *args)
54
+ @mapping.fetch(type, block).call(type, *args).freeze
54
55
  end
55
56
  end
56
57
  end
@@ -16,6 +16,13 @@ module ActiveRecord
16
16
  def default_timezone
17
17
  @timezone || ActiveRecord.default_timezone
18
18
  end
19
+
20
+ def ==(other)
21
+ super(other) && timezone == other.timezone
22
+ end
23
+
24
+ protected
25
+ attr_reader :timezone
19
26
  end
20
27
  end
21
28
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/json"
4
+
3
5
  module ActiveRecord
4
6
  module Type
5
7
  class Json < ActiveModel::Type::Value
@@ -11,11 +13,22 @@ module ActiveRecord
11
13
 
12
14
  def deserialize(value)
13
15
  return value unless value.is_a?(::String)
14
- ActiveSupport::JSON.decode(value) rescue nil
16
+ begin
17
+ ActiveSupport::JSON.decode(value)
18
+ rescue JSON::ParserError => e
19
+ # NOTE: This may hide json with duplicate keys. We don't really want to just ignore it
20
+ # but it's the best we can do in order to still allow updating columns that somehow already
21
+ # contain invalid json from some other source.
22
+ # See https://github.com/rails/rails/pull/55536
23
+ ActiveSupport.error_reporter.report(e, source: "application.active_record")
24
+ nil
25
+ end
15
26
  end
16
27
 
28
+ JSON_ENCODER = ActiveSupport::JSON::Encoding.json_encoder.new(escape: false)
29
+
17
30
  def serialize(value)
18
- ActiveSupport::JSON.encode(value) unless value.nil?
31
+ JSON_ENCODER.encode(value) unless value.nil?
19
32
  end
20
33
 
21
34
  def changed_in_place?(raw_old_value, new_value)
@@ -9,9 +9,10 @@ module ActiveRecord
9
9
 
10
10
  attr_reader :subtype, :coder
11
11
 
12
- def initialize(subtype, coder)
12
+ def initialize(subtype, coder, comparable: false)
13
13
  @subtype = subtype
14
14
  @coder = coder
15
+ @comparable = comparable
15
16
  super(subtype)
16
17
  end
17
18
 
@@ -34,9 +35,15 @@ module ActiveRecord
34
35
 
35
36
  def changed_in_place?(raw_old_value, value)
36
37
  return false if value.nil?
37
- raw_new_value = encoded(value)
38
- raw_old_value.nil? != raw_new_value.nil? ||
39
- subtype.changed_in_place?(raw_old_value, raw_new_value)
38
+
39
+ if @comparable
40
+ old_value = deserialize(raw_old_value)
41
+ old_value != value
42
+ else
43
+ raw_new_value = encoded(value)
44
+ raw_old_value.nil? != raw_new_value.nil? ||
45
+ subtype.changed_in_place?(raw_old_value, raw_new_value)
46
+ end
40
47
  end
41
48
 
42
49
  def accessor
@@ -46,7 +46,7 @@ module ActiveRecord
46
46
  end
47
47
 
48
48
  if matching_pair
49
- matching_pair.last.call(lookup_key)
49
+ matching_pair.last.call(lookup_key).freeze
50
50
  elsif @parent
51
51
  @parent.perform_fetch(lookup_key, &block)
52
52
  else
@@ -19,7 +19,8 @@ module ActiveRecord
19
19
  if schema_cache.data_source_exists?(table_name)
20
20
  column = schema_cache.columns_hash(table_name)[attr_name.to_s]
21
21
  if column
22
- type = @klass.with_connection { |connection| connection.lookup_cast_type_from_column(column) }
22
+ # TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
23
+ type = column.fetch_cast_type(@klass.lease_connection)
23
24
  end
24
25
  end
25
26
 
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  context = record_validation_context_for_association(record)
8
8
 
9
9
  if Array(value).reject { |association| valid_object?(association, context) }.any?
10
- record.errors.add(attribute, :invalid, **options.merge(value: value))
10
+ record.errors.add(attribute, :invalid, **options, value: value)
11
11
  end
12
12
  end
13
13
 
@@ -54,17 +54,17 @@ module ActiveRecord
54
54
  private
55
55
  # The check for an existing value should be run from a class that
56
56
  # isn't abstract. This means working down from the current class
57
- # (self), to the first non-abstract class. Since classes don't know
58
- # their subclasses, we have to build the hierarchy between self and
59
- # the record's class.
57
+ # (self), to the first non-abstract class.
60
58
  def find_finder_class_for(record)
61
- class_hierarchy = [record.class]
62
-
63
- while class_hierarchy.first != @klass
64
- class_hierarchy.unshift(class_hierarchy.first.superclass)
59
+ current_class = record.class
60
+ found_class = nil
61
+ loop do
62
+ found_class = current_class unless current_class.abstract_class?
63
+ break if current_class == @klass
64
+ current_class = current_class.superclass
65
65
  end
66
66
 
67
- class_hierarchy.detect { |klass| !klass.abstract_class? }
67
+ found_class
68
68
  end
69
69
 
70
70
  def validation_needed?(klass, record, attribute)
data/lib/active_record.rb CHANGED
@@ -26,9 +26,11 @@
26
26
  require "active_support"
27
27
  require "active_support/rails"
28
28
  require "active_support/ordered_options"
29
+ require "active_support/core_ext/array/conversions"
29
30
  require "active_model"
30
31
  require "arel"
31
32
  require "yaml"
33
+ require "zlib"
32
34
 
33
35
  require "active_record/version"
34
36
  require "active_record/deprecator"
@@ -51,6 +53,7 @@ module ActiveRecord
51
53
  autoload :Enum
52
54
  autoload :Explain
53
55
  autoload :FixtureSet, "active_record/fixtures"
56
+ autoload :FilterAttributeHandler
54
57
  autoload :Inheritance
55
58
  autoload :Integration
56
59
  autoload :InternalMetadata
@@ -61,7 +64,6 @@ module ActiveRecord
61
64
  autoload :ModelSchema
62
65
  autoload :NestedAttributes
63
66
  autoload :NoTouching
64
- autoload :Normalization
65
67
  autoload :Persistence
66
68
  autoload :QueryCache
67
69
  autoload :QueryLogs
@@ -86,7 +88,6 @@ module ActiveRecord
86
88
  autoload :Timestamp
87
89
  autoload :TokenFor
88
90
  autoload :TouchLater
89
- autoload :Transaction
90
91
  autoload :Transactions
91
92
  autoload :Translation
92
93
  autoload :Validations
@@ -108,6 +109,7 @@ module ActiveRecord
108
109
  autoload :Result
109
110
  autoload :StatementCache
110
111
  autoload :TableMetadata
112
+ autoload :Transaction
111
113
  autoload :Type
112
114
 
113
115
  autoload_under "relation" do
@@ -173,7 +175,8 @@ module ActiveRecord
173
175
  extend ActiveSupport::Autoload
174
176
 
175
177
  autoload :DatabaseTasks
176
- autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
178
+ autoload :AbstractTasks, "active_record/tasks/abstract_tasks"
179
+ autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks"
177
180
  autoload :PostgreSQLDatabaseTasks, "active_record/tasks/postgresql_database_tasks"
178
181
  autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks"
179
182
  end
@@ -196,6 +199,20 @@ module ActiveRecord
196
199
  singleton_class.attr_accessor :schema_cache_ignored_tables
197
200
  self.schema_cache_ignored_tables = []
198
201
 
202
+ # Checks to see if the +table_name+ is ignored by checking
203
+ # against the +schema_cache_ignored_tables+ option.
204
+ #
205
+ # ActiveRecord.schema_cache_ignored_table?(:developers)
206
+ #
207
+ def self.schema_cache_ignored_table?(table_name)
208
+ ActiveRecord.schema_cache_ignored_tables.any? do |ignored|
209
+ ignored === table_name
210
+ end
211
+ end
212
+
213
+ singleton_class.attr_accessor :database_cli
214
+ self.database_cli = { postgresql: "psql", mysql: %w[mysql mysql5], sqlite: "sqlite3" }
215
+
199
216
  singleton_class.attr_reader :default_timezone
200
217
 
201
218
  # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
@@ -244,6 +261,9 @@ module ActiveRecord
244
261
  ##
245
262
  # :singleton-method: db_warnings_ignore
246
263
  # Specify allowlist of database warnings.
264
+ # Can be a string, regular expression, or an error code from the database.
265
+ #
266
+ # ActiveRecord::Base.db_warnings_ignore = [/`SHOW WARNINGS` did not return the warnings/, "01000"]
247
267
  singleton_class.attr_accessor :db_warnings_ignore
248
268
  self.db_warnings_ignore = []
249
269
 
@@ -253,14 +273,6 @@ module ActiveRecord
253
273
  singleton_class.attr_accessor :reading_role
254
274
  self.reading_role = :reading
255
275
 
256
- def self.legacy_connection_handling=(_)
257
- raise ArgumentError, <<~MSG.squish
258
- The `legacy_connection_handling` setter was deprecated in 7.0 and removed in 7.1,
259
- but is still defined in your configuration. Please remove this call as it no longer
260
- has any effect."
261
- MSG
262
- end
263
-
264
276
  ##
265
277
  # :singleton-method: async_query_executor
266
278
  # Sets the async_query_executor for an application. By default the thread pool executor
@@ -279,6 +291,7 @@ module ActiveRecord
279
291
  def self.global_thread_pool_async_query_executor # :nodoc:
280
292
  concurrency = global_executor_concurrency || 4
281
293
  @global_thread_pool_async_query_executor ||= Concurrent::ThreadPoolExecutor.new(
294
+ name: "ActiveRecord-global-async-query-executor",
282
295
  min_threads: 0,
283
296
  max_threads: concurrency,
284
297
  max_queue: concurrency * 4,
@@ -344,28 +357,8 @@ module ActiveRecord
344
357
  singleton_class.attr_accessor :run_after_transaction_callbacks_in_order_defined
345
358
  self.run_after_transaction_callbacks_in_order_defined = false
346
359
 
347
- def self.commit_transaction_on_non_local_return
348
- ActiveRecord.deprecator.warn <<-WARNING.squish
349
- `Rails.application.config.active_record.commit_transaction_on_non_local_return`
350
- is deprecated and will be removed in Rails 8.0.
351
- WARNING
352
- end
353
-
354
- def self.commit_transaction_on_non_local_return=(value)
355
- ActiveRecord.deprecator.warn <<-WARNING.squish
356
- `Rails.application.config.active_record.commit_transaction_on_non_local_return`
357
- is deprecated and will be removed in Rails 8.0.
358
- WARNING
359
- end
360
-
361
- ##
362
- # :singleton-method: warn_on_records_fetched_greater_than
363
- # Specify a threshold for the size of query result sets. If the number of
364
- # records in the set exceeds the threshold, a warning is logged. This can
365
- # be used to identify queries which load thousands of records and
366
- # potentially cause memory bloat.
367
- singleton_class.attr_accessor :warn_on_records_fetched_greater_than
368
- self.warn_on_records_fetched_greater_than = false
360
+ singleton_class.attr_accessor :raise_on_missing_required_finder_order_columns
361
+ self.run_after_transaction_callbacks_in_order_defined = false
369
362
 
370
363
  singleton_class.attr_accessor :application_record_class
371
364
  self.application_record_class = nil
@@ -384,7 +377,8 @@ module ActiveRecord
384
377
  # specific) SQL statements. If :ruby, the schema is dumped as an
385
378
  # ActiveRecord::Schema file which can be loaded into any database that
386
379
  # supports migrations. Use :ruby if you want to have different database
387
- # adapters for, e.g., your development and test environments.
380
+ # adapters for, e.g., your development and test environments. This can be
381
+ # overridden per-database in the database configuration.
388
382
  singleton_class.attr_accessor :schema_format
389
383
  self.schema_format = :ruby
390
384
 
@@ -416,6 +410,12 @@ module ActiveRecord
416
410
  singleton_class.attr_accessor :migration_strategy
417
411
  self.migration_strategy = Migration::DefaultStrategy
418
412
 
413
+ ##
414
+ # :singleton-method: schema_versions_formatter
415
+ # Specify the formatter used by schema dumper to format versions information.
416
+ singleton_class.attr_accessor :schema_versions_formatter
417
+ self.schema_versions_formatter = Migration::DefaultSchemaVersionsFormatter
418
+
419
419
  ##
420
420
  # :singleton-method: dump_schema_after_migration
421
421
  # Specify whether schema dump should happen at the end of the
@@ -444,20 +444,6 @@ module ActiveRecord
444
444
  singleton_class.attr_accessor :verify_foreign_keys_for_fixtures
445
445
  self.verify_foreign_keys_for_fixtures = false
446
446
 
447
- def self.allow_deprecated_singular_associations_name
448
- ActiveRecord.deprecator.warn <<-WARNING.squish
449
- `Rails.application.config.active_record.allow_deprecated_singular_associations_name`
450
- is deprecated and will be removed in Rails 8.0.
451
- WARNING
452
- end
453
-
454
- def self.allow_deprecated_singular_associations_name=(value)
455
- ActiveRecord.deprecator.warn <<-WARNING.squish
456
- `Rails.application.config.active_record.allow_deprecated_singular_associations_name`
457
- is deprecated and will be removed in Rails 8.0.
458
- WARNING
459
- end
460
-
461
447
  singleton_class.attr_accessor :query_transformers
462
448
  self.query_transformers = []
463
449
 
@@ -490,6 +476,29 @@ module ActiveRecord
490
476
  singleton_class.attr_accessor :generate_secure_token_on
491
477
  self.generate_secure_token_on = :create
492
478
 
479
+ def self.deprecated_associations_options=(options)
480
+ raise ArgumentError, "deprecated_associations_options must be a hash" unless options.is_a?(Hash)
481
+
482
+ valid_keys = [:mode, :backtrace]
483
+
484
+ invalid_keys = options.keys - valid_keys
485
+ unless invalid_keys.empty?
486
+ inflected_key = invalid_keys.size == 1 ? "key" : "keys"
487
+ raise ArgumentError, "invalid deprecated_associations_options #{inflected_key} #{invalid_keys.map(&:inspect).to_sentence} (valid keys are #{valid_keys.map(&:inspect).to_sentence})"
488
+ end
489
+
490
+ options.each do |key, value|
491
+ ActiveRecord::Associations::Deprecation.send("#{key}=", value)
492
+ end
493
+ end
494
+
495
+ def self.deprecated_associations_options
496
+ {
497
+ mode: ActiveRecord::Associations::Deprecation.mode,
498
+ backtrace: ActiveRecord::Associations::Deprecation.backtrace
499
+ }
500
+ end
501
+
493
502
  def self.marshalling_format_version
494
503
  Marshalling.format_version
495
504
  end
@@ -526,6 +535,13 @@ module ActiveRecord
526
535
  }
527
536
  )
528
537
 
538
+ ##
539
+ # :singleton-method: message_verifiers
540
+ #
541
+ # ActiveSupport::MessageVerifiers instance for Active Record. If you are using
542
+ # Rails, this will be set to +Rails.application.message_verifiers+.
543
+ singleton_class.attr_accessor :message_verifiers
544
+
529
545
  def self.eager_load!
530
546
  super
531
547
  ActiveRecord::Locking.eager_load!
@@ -578,13 +594,32 @@ module ActiveRecord
578
594
  open_transactions = []
579
595
  Base.connection_handler.each_connection_pool do |pool|
580
596
  if active_connection = pool.active_connection
581
- if active_connection.current_transaction.open? && active_connection.current_transaction.joinable?
582
- open_transactions << active_connection.current_transaction
597
+ current_transaction = active_connection.current_transaction
598
+
599
+ if current_transaction.open? && current_transaction.joinable?
600
+ open_transactions << current_transaction
583
601
  end
584
602
  end
585
603
  end
586
604
  open_transactions
587
605
  end
606
+
607
+ def self.default_transaction_isolation_level=(isolation_level) # :nodoc:
608
+ ActiveSupport::IsolatedExecutionState[:active_record_transaction_isolation] = isolation_level
609
+ end
610
+
611
+ def self.default_transaction_isolation_level # :nodoc:
612
+ ActiveSupport::IsolatedExecutionState[:active_record_transaction_isolation]
613
+ end
614
+
615
+ # Sets a transaction isolation level for all connection pools within the block.
616
+ def self.with_transaction_isolation_level(isolation_level, &block)
617
+ original_level = self.default_transaction_isolation_level
618
+ self.default_transaction_isolation_level = isolation_level
619
+ yield
620
+ ensure
621
+ self.default_transaction_isolation_level = original_level
622
+ end
588
623
  end
589
624
 
590
625
  ActiveSupport.on_load(:active_record) do
@@ -3,6 +3,8 @@
3
3
  module Arel # :nodoc: all
4
4
  module AliasPredication
5
5
  def as(other)
6
+ other = other.name if other.is_a?(Symbol)
7
+
6
8
  Nodes::As.new self, Nodes::SqlLiteral.new(other, retryable: true)
7
9
  end
8
10
  end
@@ -13,12 +13,12 @@ module Arel # :nodoc: all
13
13
  self
14
14
  end
15
15
 
16
- def add_bind(bind)
16
+ def add_bind(bind, &)
17
17
  @binds << bind
18
18
  self
19
19
  end
20
20
 
21
- def add_binds(binds, proc_for_binds = nil)
21
+ def add_binds(binds, proc_for_binds = nil, &)
22
22
  @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds
23
23
  self
24
24
  end
@@ -12,7 +12,7 @@ module Arel # :nodoc: all
12
12
  @bind_index = 1
13
13
  end
14
14
 
15
- def add_bind(bind)
15
+ def add_bind(bind, &)
16
16
  self << yield(@bind_index)
17
17
  @bind_index += 1
18
18
  self
@@ -15,12 +15,12 @@ module Arel # :nodoc: all
15
15
  self
16
16
  end
17
17
 
18
- def add_bind(bind)
18
+ def add_bind(bind, &)
19
19
  bind = bind.value_for_database if bind.respond_to?(:value_for_database)
20
20
  self << quoter.quote(bind)
21
21
  end
22
22
 
23
- def add_binds(binds, proc_for_binds = nil)
23
+ def add_binds(binds, proc_for_binds = nil, &)
24
24
  self << binds.map { |bind| quoter.quote(bind) }.join(", ")
25
25
  end
26
26
 
data/lib/arel/crud.rb CHANGED
@@ -14,34 +14,31 @@ module Arel # :nodoc: all
14
14
  InsertManager.new
15
15
  end
16
16
 
17
- def compile_update(
18
- values,
19
- key = nil,
20
- having_clause = nil,
21
- group_values_columns = []
22
- )
17
+ def compile_update(values, key = nil)
23
18
  um = UpdateManager.new(source)
24
19
  um.set(values)
25
20
  um.take(limit)
26
21
  um.offset(offset)
27
22
  um.order(*orders)
28
23
  um.wheres = constraints
24
+ um.comment(comment)
29
25
  um.key = key
30
26
 
31
- um.group(group_values_columns) unless group_values_columns.empty?
32
- um.having(having_clause) unless having_clause.nil?
27
+ um.ast.groups = @ctx.groups
28
+ @ctx.havings.each { |h| um.having(h) }
33
29
  um
34
30
  end
35
31
 
36
- def compile_delete(key = nil, having_clause = nil, group_values_columns = [])
32
+ def compile_delete(key = nil)
37
33
  dm = DeleteManager.new(source)
38
34
  dm.take(limit)
39
35
  dm.offset(offset)
40
36
  dm.order(*orders)
41
37
  dm.wheres = constraints
38
+ dm.comment(comment)
42
39
  dm.key = key
43
- dm.group(group_values_columns) unless group_values_columns.empty?
44
- dm.having(having_clause) unless having_clause.nil?
40
+ dm.ast.groups = @ctx.groups
41
+ @ctx.havings.each { |h| dm.having(h) }
45
42
  dm
46
43
  end
47
44
  end