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
@@ -119,7 +119,18 @@ module ActiveRecord
119
119
  # enum :status, [ :active, :archived ], instance_methods: false
120
120
  # end
121
121
  #
122
- # If you want the enum value to be validated before saving, use the option +:validate+:
122
+ # By default, an +ArgumentError+ will be raised when assigning an invalid value:
123
+ #
124
+ # class Conversation < ActiveRecord::Base
125
+ # enum :status, [ :active, :archived ]
126
+ # end
127
+ #
128
+ # conversation = Conversation.new
129
+ #
130
+ # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
131
+ #
132
+ # If, instead, you want the enum value to be validated before saving, use the
133
+ # +:validate+ option:
123
134
  #
124
135
  # class Conversation < ActiveRecord::Base
125
136
  # enum :status, [ :active, :archived ], validate: true
@@ -136,7 +147,7 @@ module ActiveRecord
136
147
  # conversation.status = :active
137
148
  # conversation.valid? # => true
138
149
  #
139
- # It is also possible to pass additional validation options:
150
+ # You may also pass additional validation options:
140
151
  #
141
152
  # class Conversation < ActiveRecord::Base
142
153
  # enum :status, [ :active, :archived ], validate: { allow_nil: true }
@@ -152,16 +163,6 @@ module ActiveRecord
152
163
  #
153
164
  # conversation.status = :active
154
165
  # conversation.valid? # => true
155
- #
156
- # Otherwise +ArgumentError+ will raise:
157
- #
158
- # class Conversation < ActiveRecord::Base
159
- # enum :status, [ :active, :archived ]
160
- # end
161
- #
162
- # conversation = Conversation.new
163
- #
164
- # conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
165
166
  module Enum
166
167
  def self.extended(base) # :nodoc:
167
168
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
@@ -213,34 +214,16 @@ module ActiveRecord
213
214
  attr_reader :name, :mapping
214
215
  end
215
216
 
216
- def enum(name = nil, values = nil, **options)
217
- if name
218
- values, options = options, {} unless values
219
- return _enum(name, values, **options)
220
- end
221
-
222
- definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default, :_instance_methods)
223
- options.transform_keys! { |key| :"#{key[1..-1]}" }
224
-
225
- definitions.each { |name, values| _enum(name, values, **options) }
226
-
227
- ActiveRecord.deprecator.warn(<<~MSG)
228
- Defining enums with keyword arguments is deprecated and will be removed
229
- in Rails 8.0. Positional arguments should be used instead:
230
-
231
- #{definitions.map { |name, values| "enum :#{name}, #{values}" }.join("\n")}
232
- MSG
217
+ def enum(name, values = nil, **options)
218
+ values, options = options, {} unless values
219
+ _enum(name, values, **options)
233
220
  end
234
221
 
235
222
  private
236
- def inherited(base)
237
- base.defined_enums = defined_enums.deep_dup
238
- super
239
- end
240
-
241
223
  def _enum(name, values, prefix: nil, suffix: nil, scopes: true, instance_methods: true, validate: false, **options)
242
- assert_valid_enum_definition_values(values)
224
+ values = assert_valid_enum_definition_values(values)
243
225
  assert_valid_enum_options(options)
226
+
244
227
  # statuses = { }
245
228
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
246
229
  name = name.to_s
@@ -304,6 +287,11 @@ module ActiveRecord
304
287
  enum_values.freeze
305
288
  end
306
289
 
290
+ def inherited(base)
291
+ base.defined_enums = defined_enums.deep_dup
292
+ super
293
+ end
294
+
307
295
  class EnumMethods < Module # :nodoc:
308
296
  def initialize(klass)
309
297
  @klass = klass
@@ -354,6 +342,20 @@ module ActiveRecord
354
342
  if values.keys.any?(&:blank?)
355
343
  raise ArgumentError, "Enum values #{values} must not contain a blank name."
356
344
  end
345
+
346
+ values = values.transform_values do |value|
347
+ value.is_a?(Symbol) ? value.name : value
348
+ end
349
+
350
+ values.each_value do |value|
351
+ case value
352
+ when String, Integer, Float, true, false, nil
353
+ # noop
354
+ else
355
+ raise ArgumentError, "Enum values #{values} must be only booleans, integers, floats, symbols or strings, got: #{value.class}"
356
+ end
357
+ end
358
+
357
359
  when Array
358
360
  if values.empty?
359
361
  raise ArgumentError, "Enum values #{values} must not be empty."
@@ -369,6 +371,8 @@ module ActiveRecord
369
371
  else
370
372
  raise ArgumentError, "Enum values #{values} must be either a non-empty hash or an array."
371
373
  end
374
+
375
+ values
372
376
  end
373
377
 
374
378
  def assert_valid_enum_options(options)
@@ -380,25 +384,25 @@ module ActiveRecord
380
384
 
381
385
  ENUM_CONFLICT_MESSAGE = \
382
386
  "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
383
- "this will generate a %{type} method \"%{method}\", which is already defined " \
387
+ "this will generate %{type} method \"%{method}\", which is already defined " \
384
388
  "by %{source}."
385
389
  private_constant :ENUM_CONFLICT_MESSAGE
386
390
 
387
391
  def detect_enum_conflict!(enum_name, method_name, klass_method = false)
388
392
  if klass_method && dangerous_class_method?(method_name)
389
- raise_conflict_error(enum_name, method_name, type: "class")
393
+ raise_conflict_error(enum_name, method_name, "a class")
390
394
  elsif klass_method && method_defined_within?(method_name, Relation)
391
- raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
395
+ raise_conflict_error(enum_name, method_name, "a class", source: Relation.name)
392
396
  elsif klass_method && method_name.to_sym == :id
393
- raise_conflict_error(enum_name, method_name)
397
+ raise_conflict_error(enum_name, method_name, "an instance")
394
398
  elsif !klass_method && dangerous_attribute_method?(method_name)
395
- raise_conflict_error(enum_name, method_name)
399
+ raise_conflict_error(enum_name, method_name, "an instance")
396
400
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
397
- raise_conflict_error(enum_name, method_name, source: "another enum")
401
+ raise_conflict_error(enum_name, method_name, "an instance", source: "another enum")
398
402
  end
399
403
  end
400
404
 
401
- def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record")
405
+ def raise_conflict_error(enum_name, method_name, type, source: "Active Record")
402
406
  raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
403
407
  enum: enum_name,
404
408
  klass: name,
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/deprecation"
4
3
 
5
4
  module ActiveRecord
6
5
  include ActiveSupport::Deprecation::DeprecatedConstantAccessor
@@ -13,7 +12,7 @@ module ActiveRecord
13
12
 
14
13
  # Raised when the single-table inheritance mechanism fails to locate the subclass
15
14
  # (for example due to improper usage of column that
16
- # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
15
+ # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema.inheritance_column]
17
16
  # points to).
18
17
  class SubclassNotFound < ActiveRecordError
19
18
  end
@@ -84,6 +83,19 @@ module ActiveRecord
84
83
  class ConnectionTimeoutError < ConnectionNotEstablished
85
84
  end
86
85
 
86
+ # Raised when a database connection pool is requested but
87
+ # has not been defined.
88
+ class ConnectionNotDefined < ConnectionNotEstablished
89
+ def initialize(message = nil, connection_name: nil, role: nil, shard: nil)
90
+ super(message)
91
+ @connection_name = connection_name
92
+ @role = role
93
+ @shard = shard
94
+ end
95
+
96
+ attr_reader :connection_name, :role, :shard
97
+ end
98
+
87
99
  # Raised when connection to the database could not been established because it was not
88
100
  # able to connect to the host or when the authorization failed.
89
101
  class DatabaseConnectionError < ConnectionNotEstablished
@@ -280,6 +292,14 @@ module ActiveRecord
280
292
  class NotNullViolation < StatementInvalid
281
293
  end
282
294
 
295
+ # Raised when a record cannot be inserted or updated because it would violate a check constraint.
296
+ class CheckViolation < StatementInvalid
297
+ end
298
+
299
+ # Raised when a record cannot be inserted or updated because it would violate an exclusion constraint.
300
+ class ExclusionViolation < StatementInvalid
301
+ end
302
+
283
303
  # Raised when a record cannot be inserted or updated because a value too long for a column type.
284
304
  class ValueTooLong < StatementInvalid
285
305
  end
@@ -326,15 +346,15 @@ module ActiveRecord
326
346
  class << self
327
347
  def db_error(db_name)
328
348
  NoDatabaseError.new(<<~MSG)
329
- We could not find your database: #{db_name}. Available database configurations can be found in config/database.yml.
349
+ Database not found: #{db_name}. Available database configurations can be found in config/database.yml.
330
350
 
331
351
  To resolve this error:
332
352
 
333
- - Did you not create the database, or did you delete it? To create the database, run:
353
+ - Create the database by running:
334
354
 
335
355
  bin/rails db:create
336
356
 
337
- - Has the database name changed? Verify that config/database.yml contains the correct database name.
357
+ - Verify that config/database.yml contains the correct database name.
338
358
  MSG
339
359
  end
340
360
  end
@@ -431,7 +451,7 @@ module ActiveRecord
431
451
  UnknownAttributeError = ActiveModel::UnknownAttributeError
432
452
 
433
453
  # Raised when an error occurred while doing a mass assignment to an attribute through the
434
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
454
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
435
455
  # The exception has an +attribute+ property that is the name of the offending attribute.
436
456
  class AttributeAssignmentError < ActiveRecordError
437
457
  attr_reader :exception, :attribute
@@ -444,7 +464,7 @@ module ActiveRecord
444
464
  end
445
465
 
446
466
  # Raised when there are multiple errors while doing a mass assignment through the
447
- # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
467
+ # {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=]
448
468
  # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
449
469
  # objects, each corresponding to the error while assigning to an attribute.
450
470
  class MultiparameterAssignmentErrors < ActiveRecordError
@@ -477,6 +497,7 @@ module ActiveRecord
477
497
  # end
478
498
  #
479
499
  # relation = Task.all
500
+ # relation.load
480
501
  # relation.loaded? # => true
481
502
  #
482
503
  # # Methods which try to mutate a loaded relation fail.
@@ -484,11 +505,6 @@ module ActiveRecord
484
505
  # relation.limit!(5) # => ActiveRecord::UnmodifiableRelation
485
506
  class UnmodifiableRelation < ActiveRecordError
486
507
  end
487
- deprecate_constant(
488
- :ImmutableRelation,
489
- "ActiveRecord::UnmodifiableRelation",
490
- deprecator: ActiveRecord.deprecator
491
- )
492
508
 
493
509
  # TransactionIsolationError will be raised under the following conditions:
494
510
  #
@@ -544,6 +560,11 @@ module ActiveRecord
544
560
  class Deadlocked < TransactionRollbackError
545
561
  end
546
562
 
563
+ # MissingRequiredOrderError is raised when a relation requires ordering but
564
+ # lacks any +order+ values in scope or any model order columns to use.
565
+ class MissingRequiredOrderError < ActiveRecordError
566
+ end
567
+
547
568
  # IrreversibleOrderError is raised when a relation's order is too complex for
548
569
  # +reverse_order+ to automatically reverse.
549
570
  class IrreversibleOrderError < ActiveRecordError
@@ -601,6 +622,9 @@ module ActiveRecord
601
622
  # the database version cannot be determined.
602
623
  class DatabaseVersionError < ActiveRecordError
603
624
  end
625
+
626
+ class DeprecatedAssociationError < ActiveRecordError
627
+ end
604
628
  end
605
629
 
606
630
  require "active_record/associations/errors"
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  # Executes the block with the collect flag enabled. Queries are collected
8
8
  # asynchronously by the subscriber and returned.
9
9
  def collecting_queries_for_explain # :nodoc:
10
- ExplainRegistry.collect = true
10
+ ExplainRegistry.start
11
11
  yield
12
12
  ExplainRegistry.queries
13
13
  ensure
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/module/delegation"
4
3
 
5
4
  module ActiveRecord
6
5
  # This is a thread locals registry for EXPLAIN. For example
@@ -9,8 +8,53 @@ module ActiveRecord
9
8
  #
10
9
  # returns the collected queries local to the current thread.
11
10
  class ExplainRegistry # :nodoc:
11
+ class Subscriber
12
+ MUTEX = Mutex.new
13
+ @subscribed = false
14
+
15
+ class << self
16
+ def ensure_subscribed
17
+ return if @subscribed
18
+ MUTEX.synchronize do
19
+ return if @subscribed
20
+
21
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
22
+ @subscribed = true
23
+ end
24
+ end
25
+ end
26
+
27
+ def start(name, id, payload)
28
+ # unused
29
+ end
30
+
31
+ def finish(name, id, payload)
32
+ if ExplainRegistry.collect? && !ignore_payload?(payload)
33
+ ExplainRegistry.queries << payload.values_at(:sql, :binds)
34
+ end
35
+ end
36
+
37
+ def silenced?(_name)
38
+ !ExplainRegistry.collect?
39
+ end
40
+
41
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
42
+ # our own EXPLAINs no matter how loopingly beautiful that would be.
43
+ #
44
+ # On the other hand, we want to monitor the performance of our real database
45
+ # queries, not the performance of the access to the query cache.
46
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
47
+ EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i
48
+ def ignore_payload?(payload)
49
+ payload[:exception] ||
50
+ payload[:cached] ||
51
+ IGNORED_PAYLOADS.include?(payload[:name]) ||
52
+ !payload[:sql].match?(EXPLAINED_SQLS)
53
+ end
54
+ end
55
+
12
56
  class << self
13
- delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
57
+ delegate :start, :reset, :collect, :collect=, :collect?, :queries, to: :instance
14
58
 
15
59
  private
16
60
  def instance
@@ -25,6 +69,11 @@ module ActiveRecord
25
69
  reset
26
70
  end
27
71
 
72
+ def start
73
+ Subscriber.ensure_subscribed
74
+ @collect = true
75
+ end
76
+
28
77
  def collect?
29
78
  @collect
30
79
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class FilterAttributeHandler # :nodoc:
5
+ class << self
6
+ def on_sensitive_attribute_declared(&block)
7
+ @sensitive_attribute_declaration_listeners ||= Concurrent::Array.new
8
+ @sensitive_attribute_declaration_listeners << block
9
+ end
10
+
11
+ def sensitive_attribute_was_declared(klass, list)
12
+ @sensitive_attribute_declaration_listeners&.each do |block|
13
+ block.call(klass, list)
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ @attributes_by_class = Concurrent::Map.new
21
+ @collecting = true
22
+ end
23
+
24
+ def enable
25
+ install_collecting_hook
26
+
27
+ apply_collected_attributes
28
+ @collecting = false
29
+ end
30
+
31
+ private
32
+ attr_reader :app
33
+
34
+ def install_collecting_hook
35
+ self.class.on_sensitive_attribute_declared do |klass, list|
36
+ attribute_was_declared(klass, list)
37
+ end
38
+ end
39
+
40
+ def attribute_was_declared(klass, list)
41
+ if collecting?
42
+ collect_for_later(klass, list)
43
+ else
44
+ apply_filter(klass, list)
45
+ end
46
+ end
47
+
48
+ def apply_collected_attributes
49
+ @attributes_by_class.each do |klass, list|
50
+ apply_filter(klass, list)
51
+ end
52
+ end
53
+
54
+ def collecting?
55
+ @collecting
56
+ end
57
+
58
+ def collect_for_later(klass, list)
59
+ @attributes_by_class[klass] ||= Concurrent::Array.new
60
+ @attributes_by_class[klass] += list
61
+ end
62
+
63
+ def apply_filter(klass, list)
64
+ list.each do |attribute|
65
+ next if klass.abstract_class? || klass == Base
66
+
67
+ klass_name = klass.name ? klass.model_name.element : nil
68
+ filter = [klass_name, attribute.to_s].compact.join(".")
69
+ app.config.filter_parameters << filter unless app.config.filter_parameters.include?(filter)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -193,8 +193,25 @@ module ActiveRecord
193
193
 
194
194
  targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
195
195
  joins = targets.map do |target|
196
- join = { lhs_key => @row[model_metadata.primary_key_name],
197
- rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
196
+ join = {}
197
+
198
+ if rhs_key.is_a?(Array)
199
+ composite_key = ActiveRecord::FixtureSet.composite_identify(target, rhs_key)
200
+ composite_key.each do |column, value|
201
+ join[column] = value
202
+ end
203
+ else
204
+ join[rhs_key] = ActiveRecord::FixtureSet.identify(target, column_type)
205
+ end
206
+
207
+ if lhs_key.is_a?(Array)
208
+ lhs_key.zip(model_metadata.primary_key_name).each do |fkey, pkey|
209
+ join[fkey] = @row[pkey]
210
+ end
211
+ else
212
+ join[lhs_key] = @row[model_metadata.primary_key_name]
213
+ end
214
+
198
215
  association.timestamp_column_names.each do |col|
199
216
  join[col] = @now
200
217
  end
@@ -2,8 +2,6 @@
2
2
 
3
3
  require "erb"
4
4
  require "yaml"
5
- require "zlib"
6
- require "set"
7
5
  require "active_support/dependencies"
8
6
  require "active_support/core_ext/digest/uuid"
9
7
  require "active_record/test_fixtures"
@@ -244,10 +242,10 @@ module ActiveRecord
244
242
  # and one for the humans. Why don't we generate the primary key instead?
245
243
  # Hashing each fixture's label yields a consistent ID:
246
244
  #
247
- # george: # generated id: 503576764
245
+ # george: # generated id: 380982691
248
246
  # name: George the Monkey
249
247
  #
250
- # reginald: # generated id: 324201669
248
+ # reginald: # generated id: 41001176
251
249
  # name: Reginald the Pirate
252
250
  #
253
251
  # Active Record looks at the fixture's model class, discovers the correct
@@ -100,17 +100,21 @@ module ActiveRecord
100
100
  def execute_or_skip
101
101
  return unless pending?
102
102
 
103
- @pool.with_connection do |connection|
104
- return unless @mutex.try_lock
105
- begin
106
- if pending?
107
- @event_buffer = EventBuffer.new(self, @instrumenter)
108
- connection.with_instrumenter(@event_buffer) do
103
+ @session.synchronize do
104
+ return unless pending?
105
+
106
+ @pool.with_connection do |connection|
107
+ return unless @mutex.try_lock
108
+ begin
109
+ if pending?
110
+ @event_buffer = EventBuffer.new(self, @instrumenter)
111
+ ActiveSupport::IsolatedExecutionState[:active_record_instrumenter] = @event_buffer
112
+
109
113
  execute_query(connection, async: true)
110
114
  end
115
+ ensure
116
+ @mutex.unlock
111
117
  end
112
- ensure
113
- @mutex.unlock
114
118
  end
115
119
  end
116
120
  end
@@ -163,7 +167,7 @@ module ActiveRecord
163
167
  end
164
168
 
165
169
  def exec_query(connection, *args, **kwargs)
166
- connection.internal_exec_query(*args, **kwargs)
170
+ connection.raw_exec_query(*args, **kwargs)
167
171
  end
168
172
 
169
173
  class SelectAll < FutureResult # :nodoc:
@@ -7,10 +7,10 @@ module ActiveRecord
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
10
+ MAJOR = 8
11
+ MINOR = 1
12
12
  TINY = 2
13
- PRE = "1"
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -97,7 +97,7 @@ module ActiveRecord
97
97
  # Returns the first class in the inheritance hierarchy that descends from either an
98
98
  # abstract class or from <tt>ActiveRecord::Base</tt>.
99
99
  #
100
- # Consider the following behaviour:
100
+ # Consider the following behavior:
101
101
  #
102
102
  # class ApplicationRecord < ActiveRecord::Base
103
103
  # self.abstract_class = true
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  def execute(relation, ...)
12
12
  relation.model.with_connection do |c|
13
13
  new(relation, c, ...).execute
14
- end
14
+ end.tap { relation.reset }
15
15
  end
16
16
  end
17
17
 
@@ -225,7 +225,7 @@ module ActiveRecord
225
225
  class Builder # :nodoc:
226
226
  attr_reader :model
227
227
 
228
- delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, to: :insert_all
228
+ delegate :skip_duplicates?, :update_duplicates?, :keys, :keys_including_timestamps, :record_timestamps?, :primary_keys, to: :insert_all
229
229
 
230
230
  def initialize(insert_all)
231
231
  @insert_all, @model, @connection = insert_all, insert_all.model, insert_all.connection
@@ -236,11 +236,16 @@ module ActiveRecord
236
236
  end
237
237
 
238
238
  def values_list
239
- types = extract_types_from_columns_on(model.table_name, keys: keys_including_timestamps)
239
+ types = extract_types_for(keys_including_timestamps)
240
240
 
241
241
  values_list = insert_all.map_key_with_value do |key, value|
242
- next value if Arel::Nodes::SqlLiteral === value
243
- connection.with_yaml_fallback(types[key].serialize(value))
242
+ if Arel::Nodes::SqlLiteral === value
243
+ value
244
+ elsif primary_keys.include?(key) && value.nil?
245
+ connection.default_insert_value(model.columns_hash[key])
246
+ else
247
+ ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
248
+ end
244
249
  end
245
250
 
246
251
  connection.visitor.compile(Arel::Nodes::ValuesList.new(values_list))
@@ -303,8 +308,8 @@ module ActiveRecord
303
308
  format_columns(insert_all.keys_including_timestamps)
304
309
  end
305
310
 
306
- def extract_types_from_columns_on(table_name, keys:)
307
- columns = @model.schema_cache.columns_hash(table_name)
311
+ def extract_types_for(keys)
312
+ columns = @model.columns_hash
308
313
 
309
314
  unknown_column = (keys - columns.keys).first
310
315
  raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred
10
10
  # and the update is ignored.
11
11
  #
12
- # Check out +ActiveRecord::Locking::Pessimistic+ for an alternative.
12
+ # Check out ActiveRecord::Locking::Pessimistic for an alternative.
13
13
  #
14
14
  # == Usage
15
15
  #
@@ -101,6 +101,13 @@ module ActiveRecord
101
101
  attribute_names = attribute_names.dup if attribute_names.frozen?
102
102
  attribute_names << locking_column
103
103
 
104
+ if self[locking_column].nil?
105
+ raise(<<-MSG.squish)
106
+ For optimistic locking, locking_column ('#{locking_column}') can't be nil.
107
+ Are you missing a default value or validation on '#{locking_column}'?
108
+ MSG
109
+ end
110
+
104
111
  self[locking_column] += 1
105
112
 
106
113
  affected_rows = self.class._update_record(
@@ -67,6 +67,10 @@ module ActiveRecord
67
67
  # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
68
68
  # the locked record.
69
69
  def lock!(lock = true)
70
+ if self.class.current_preventing_writes
71
+ raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
72
+ end
73
+
70
74
  if persisted?
71
75
  if has_changes_to_save?
72
76
  raise(<<-MSG.squish)
@@ -79,6 +83,7 @@ module ActiveRecord
79
83
 
80
84
  reload(lock: lock)
81
85
  end
86
+
82
87
  self
83
88
  end
84
89
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
- class LogSubscriber < ActiveSupport::LogSubscriber
4
+ class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
5
5
  IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
6
6
 
7
7
  class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
@@ -126,18 +126,8 @@ module ActiveRecord
126
126
  end
127
127
  end
128
128
 
129
- if Thread.respond_to?(:each_caller_location)
130
- def query_source_location
131
- Thread.each_caller_location do |location|
132
- frame = backtrace_cleaner.clean_frame(location)
133
- return frame if frame
134
- end
135
- nil
136
- end
137
- else
138
- def query_source_location
139
- backtrace_cleaner.clean(caller(1).lazy).first
140
- end
129
+ def query_source_location
130
+ backtrace_cleaner.first_clean_frame
141
131
  end
142
132
 
143
133
  def filter(name, value)