activerecord 7.2.4 → 8.1.4

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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  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 +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -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/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -48,7 +48,7 @@ module ActiveRecord
48
48
  # way of creating a namespace for tables in a shared database. By default, the prefix is the
49
49
  # empty string.
50
50
  #
51
- # If you are organising your models within modules you can add a prefix to the models within
51
+ # If you are organizing your models within modules you can add a prefix to the models within
52
52
  # a namespace by defining a singleton method in the parent module called table_name_prefix which
53
53
  # returns your chosen prefix.
54
54
 
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  # Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
66
66
  # "people_basecamp"). By default, the suffix is the empty string.
67
67
  #
68
- # If you are organising your models within modules, you can add a suffix to the models within
68
+ # If you are organizing your models within modules, you can add a suffix to the models within
69
69
  # a namespace by defining a singleton method in the parent module called table_name_suffix which
70
70
  # returns your chosen suffix.
71
71
 
@@ -113,17 +113,19 @@ module ActiveRecord
113
113
  # :singleton-method: implicit_order_column
114
114
  # :call-seq: implicit_order_column
115
115
  #
116
- # The name of the column records are ordered by if no explicit order clause
116
+ # The name of the column(s) records are ordered by if no explicit order clause
117
117
  # is used during an ordered finder call. If not set the primary key is used.
118
118
 
119
119
  ##
120
120
  # :singleton-method: implicit_order_column=
121
121
  # :call-seq: implicit_order_column=(column_name)
122
122
  #
123
- # Sets the column to sort records by when no explicit order clause is used
124
- # during an ordered finder call. Useful when the primary key is not an
125
- # auto-incrementing integer, for example when it's a UUID. Records are subsorted
126
- # by the primary key if it exists to ensure deterministic results.
123
+ # Sets the column(s) to sort records by when no explicit order clause is used
124
+ # during an ordered finder call. Useful for models where the primary key isn't an
125
+ # auto-incrementing integer (such as UUID).
126
+ #
127
+ # By default, records are subsorted by primary key to ensure deterministic results.
128
+ # To disable this subsort behavior, set `implicit_order_column` to `["column_name", nil]`.
127
129
 
128
130
  ##
129
131
  # :singleton-method: immutable_strings_by_default=
@@ -179,6 +181,7 @@ module ActiveRecord
179
181
  self.protected_environments = ["production"]
180
182
 
181
183
  self.ignored_columns = [].freeze
184
+ self.only_columns = [].freeze
182
185
 
183
186
  delegate :type_for_attribute, :column_for_attribute, to: :class
184
187
 
@@ -276,15 +279,14 @@ module ActiveRecord
276
279
  end
277
280
 
278
281
  @table_name = value
279
- @quoted_table_name = nil
280
282
  @arel_table = nil
281
283
  @sequence_name = nil unless @explicit_sequence_name
282
284
  @predicate_builder = nil
283
285
  end
284
286
 
285
- # Returns a quoted version of the table name, used to construct SQL statements.
287
+ # Returns a quoted version of the table name.
286
288
  def quoted_table_name
287
- @quoted_table_name ||= adapter_class.quote_table_name(table_name)
289
+ adapter_class.quote_table_name(table_name)
288
290
  end
289
291
 
290
292
  # Computes the table name, (re)sets it internally, and returns it.
@@ -333,6 +335,12 @@ module ActiveRecord
333
335
  @ignored_columns || superclass.ignored_columns
334
336
  end
335
337
 
338
+ # The list of columns names the model should allow. Only columns are used to define
339
+ # attribute accessors, and are referenced in SQL queries.
340
+ def only_columns
341
+ @only_columns || superclass.only_columns
342
+ end
343
+
336
344
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
337
345
  # accessors defined, and won't be referenced in SQL queries.
338
346
  #
@@ -365,10 +373,17 @@ module ActiveRecord
365
373
  # user = Project.create!(name: "First Project")
366
374
  # user.category # => raises NoMethodError
367
375
  def ignored_columns=(columns)
376
+ check_model_columns(@only_columns.present?)
368
377
  reload_schema_from_cache
369
378
  @ignored_columns = columns.map(&:to_s).freeze
370
379
  end
371
380
 
381
+ def only_columns=(columns)
382
+ check_model_columns(@ignored_columns.present?)
383
+ reload_schema_from_cache
384
+ @only_columns = columns.map(&:to_s).freeze
385
+ end
386
+
372
387
  def sequence_name
373
388
  if base_class?
374
389
  @sequence_name ||= reset_sequence_name
@@ -420,7 +435,7 @@ module ActiveRecord
420
435
 
421
436
  def attributes_builder # :nodoc:
422
437
  @attributes_builder ||= begin
423
- defaults = _default_attributes.except(*(column_names - [primary_key]))
438
+ defaults = _default_attributes.except(*(column_names - Array(primary_key)))
424
439
  ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
425
440
  end
426
441
  end
@@ -489,7 +504,7 @@ module ActiveRecord
489
504
  # and columns used for single table inheritance have been removed.
490
505
  def content_columns
491
506
  @content_columns ||= columns.reject do |c|
492
- c.name == primary_key ||
507
+ Array(primary_key).include?(c.name) ||
493
508
  c.name == inheritance_column ||
494
509
  c.name.end_with?("_id", "_count")
495
510
  end.freeze
@@ -502,7 +517,7 @@ module ActiveRecord
502
517
  # when just after creating a table you want to populate it with some default
503
518
  # values, e.g.:
504
519
  #
505
- # class CreateJobLevels < ActiveRecord::Migration[7.2]
520
+ # class CreateJobLevels < ActiveRecord::Migration[8.1]
506
521
  # def up
507
522
  # create_table :job_levels do |t|
508
523
  # t.integer :id
@@ -578,6 +593,7 @@ module ActiveRecord
578
593
  child_class.reload_schema_from_cache(false)
579
594
  child_class.class_eval do
580
595
  @ignored_columns = nil
596
+ @only_columns = nil
581
597
  end
582
598
  end
583
599
 
@@ -591,7 +607,11 @@ module ActiveRecord
591
607
  end
592
608
 
593
609
  columns_hash = schema_cache.columns_hash(table_name)
594
- columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
610
+ if only_columns.present?
611
+ columns_hash = columns_hash.slice(*only_columns)
612
+ elsif ignored_columns.present?
613
+ columns_hash = columns_hash.except(*ignored_columns)
614
+ end
595
615
  @columns_hash = columns_hash.freeze
596
616
 
597
617
  _default_attributes # Precompute to cache DB-dependent attribute types
@@ -621,7 +641,8 @@ module ActiveRecord
621
641
  end
622
642
 
623
643
  def type_for_column(connection, column)
624
- type = connection.lookup_cast_type_from_column(column)
644
+ # TODO: Remove the need for a connection after we release 8.1.
645
+ type = column.fetch_cast_type(connection)
625
646
 
626
647
  if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
627
648
  type = type.to_immutable_string
@@ -629,6 +650,10 @@ module ActiveRecord
629
650
 
630
651
  type
631
652
  end
653
+
654
+ def check_model_columns(columns_present)
655
+ raise ArgumentError, "You can not use both only_columns and ignored_columns in the same model." if columns_present
656
+ end
632
657
  end
633
658
  end
634
659
  end
@@ -103,7 +103,7 @@ module ActiveRecord
103
103
  #
104
104
  # class Member < ActiveRecord::Base
105
105
  # has_many :posts
106
- # accepts_nested_attributes_for :posts
106
+ # accepts_nested_attributes_for :posts, allow_destroy: true
107
107
  # end
108
108
  #
109
109
  # You can now set or update attributes on the associated posts through
@@ -112,7 +112,8 @@ module ActiveRecord
112
112
  #
113
113
  # For each hash that does _not_ have an <tt>id</tt> key a new record will
114
114
  # be instantiated, unless the hash also contains a <tt>_destroy</tt> key
115
- # that evaluates to +true+.
115
+ # that evaluates to +true+ and the <tt>:allow_destroy</tt> option is
116
+ # enabled.
116
117
  #
117
118
  # params = { member: {
118
119
  # name: 'joe', posts_attributes: [
@@ -313,8 +314,9 @@ module ActiveRecord
313
314
  # that checks whether a record should be built for a certain attribute
314
315
  # hash. The hash is passed to the supplied Proc or the method
315
316
  # and it should return either +true+ or +false+. When no +:reject_if+
316
- # is specified, a record will be built for all attribute hashes that
317
- # do not have a <tt>_destroy</tt> value that evaluates to true.
317
+ # is specified, a record will be built for all attribute hashes, unless
318
+ # the <tt>:allow_destroy</tt> option is enabled and the hash has a
319
+ # <tt>_destroy</tt> value that evaluates to true.
318
320
  # Passing <tt>:all_blank</tt> instead of a Proc will create a proc
319
321
  # that will reject a record where all the attributes are blank excluding
320
322
  # any value for +_destroy+.
@@ -387,6 +389,8 @@ module ActiveRecord
387
389
  generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
388
390
  silence_redefinition_of_method :#{association_name}_attributes=
389
391
  def #{association_name}_attributes=(attributes)
392
+ association = association(:#{association_name})
393
+ deprecated_associations_api_guard(association, __method__)
390
394
  assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
391
395
  end
392
396
  eoruby
@@ -494,8 +498,6 @@ module ActiveRecord
494
498
  raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}"
495
499
  end
496
500
 
497
- check_record_limit!(options[:limit], attributes_collection)
498
-
499
501
  if attributes_collection.is_a? Hash
500
502
  keys = attributes_collection.keys
501
503
  attributes_collection = if keys.include?("id") || keys.include?(:id)
@@ -505,6 +507,8 @@ module ActiveRecord
505
507
  end
506
508
  end
507
509
 
510
+ check_record_limit!(options[:limit], attributes_collection)
511
+
508
512
  association = association(association_name)
509
513
 
510
514
  existing_records = if association.loaded?
@@ -524,12 +528,12 @@ module ActiveRecord
524
528
  unless reject_new_record?(association_name, attributes)
525
529
  association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
526
530
  end
527
- elsif existing_record = find_record_by_id(existing_records, attributes["id"])
531
+ elsif existing_record = find_record_by_id(association.klass, existing_records, attributes["id"])
528
532
  unless call_reject_if(association_name, attributes)
529
533
  # Make sure we are operating on the actual object which is in the association's
530
534
  # proxy_target array (either by finding it, or adding it if not found)
531
535
  # Take into account that the proxy_target may have changed due to callbacks
532
- target_record = find_record_by_id(association.target, attributes["id"])
536
+ target_record = find_record_by_id(association.klass, association.target, attributes["id"])
533
537
  if target_record
534
538
  existing_record = target_record
535
539
  else
@@ -621,10 +625,8 @@ module ActiveRecord
621
625
  model, "id", record_id)
622
626
  end
623
627
 
624
- def find_record_by_id(records, id)
625
- return if records.empty?
626
-
627
- if records.first.class.composite_primary_key?
628
+ def find_record_by_id(klass, records, id)
629
+ if klass.composite_primary_key?
628
630
  id = Array(id).map(&:to_s)
629
631
  records.find { |record| Array(record.id).map(&:to_s) == id }
630
632
  else
@@ -248,18 +248,16 @@ module ActiveRecord
248
248
 
249
249
  im = Arel::InsertManager.new(arel_table)
250
250
 
251
- with_connection do |c|
252
- if values.empty?
253
- im.insert(connection.empty_insert_statement_value(primary_key))
254
- else
255
- im.insert(values.transform_keys { |name| arel_table[name] })
256
- end
257
-
258
- connection.insert(
259
- im, "#{self} Create", primary_key || false, primary_key_value,
260
- returning: returning
261
- )
251
+ if values.empty?
252
+ im.insert(connection.empty_insert_statement_value(primary_key))
253
+ else
254
+ im.insert(values.transform_keys { |name| arel_table[name] })
262
255
  end
256
+
257
+ connection.insert(
258
+ im, "#{self} Create", primary_key || false, primary_key_value,
259
+ returning: returning
260
+ )
263
261
  end
264
262
 
265
263
  def _update_record(values, constraints) # :nodoc:
@@ -494,7 +492,9 @@ module ActiveRecord
494
492
  becoming.instance_variable_set(:@attributes, @attributes)
495
493
  becoming.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
496
494
  becoming.instance_variable_set(:@new_record, new_record?)
495
+ becoming.instance_variable_set(:@previously_new_record, previously_new_record?)
497
496
  becoming.instance_variable_set(:@destroyed, destroyed?)
497
+ becoming.instance_variable_set(:@marked_for_destruction, marked_for_destruction?)
498
498
  becoming.errors.copy!(errors)
499
499
  end
500
500
 
@@ -583,8 +583,8 @@ module ActiveRecord
583
583
  end
584
584
 
585
585
  # Equivalent to <code>update_columns(name => value)</code>.
586
- def update_column(name, value)
587
- update_columns(name => value)
586
+ def update_column(name, value, touch: nil)
587
+ update_columns(name => value, touch: touch)
588
588
  end
589
589
 
590
590
  # Updates the attributes directly in the database issuing an UPDATE SQL
@@ -598,11 +598,25 @@ module ActiveRecord
598
598
  #
599
599
  # * \Validations are skipped.
600
600
  # * \Callbacks are skipped.
601
- # * +updated_at+/+updated_on+ are not updated.
601
+ # * +updated_at+/+updated_on+ are updated if the +touch+ option is set to +true+.
602
602
  # * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
603
603
  #
604
604
  # This method raises an ActiveRecord::ActiveRecordError when called on new
605
605
  # objects, or when at least one of the attributes is marked as readonly.
606
+ #
607
+ # ==== Parameters
608
+ #
609
+ # * <tt>:touch</tt> option - Touch the timestamp columns when updating.
610
+ # * If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
611
+ #
612
+ # ==== Examples
613
+ #
614
+ # # Update a single attribute.
615
+ # user.update_columns(last_request_at: Time.current)
616
+ #
617
+ # # Update with touch option.
618
+ # user.update_columns(last_request_at: Time.current, touch: true)
619
+
606
620
  def update_columns(attributes)
607
621
  raise ActiveRecordError, "cannot update a new record" if new_record?
608
622
  raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
@@ -614,6 +628,15 @@ module ActiveRecord
614
628
  verify_readonly_attribute(name) || name
615
629
  end
616
630
 
631
+ touch = attributes.delete("touch")
632
+ if touch
633
+ names = touch if touch != true
634
+ names = Array.wrap(names)
635
+ options = names.extract_options!
636
+ touch_updates = self.class.touch_attributes_with_time(*names, **options)
637
+ attributes.with_defaults!(touch_updates) unless touch_updates.empty?
638
+ end
639
+
617
640
  update_constraints = _query_constraints_hash
618
641
  attributes = attributes.each_with_object({}) do |(k, v), h|
619
642
  h[k] = @attributes.write_cast_value(k, v)
@@ -642,8 +665,15 @@ module ActiveRecord
642
665
  # This means that any other modified attributes will still be dirty.
643
666
  # Validations and callbacks are skipped. Supports the +touch+ option from
644
667
  # +update_counters+, see that for more.
668
+ #
669
+ # This method raises an ActiveRecord::ActiveRecordError when called on new
670
+ # objects, or when at least one of the attributes is marked as readonly.
671
+ #
645
672
  # Returns +self+.
646
673
  def increment!(attribute, by = 1, touch: nil)
674
+ raise ActiveRecordError, "cannot update a new record" if new_record?
675
+ raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
676
+
647
677
  increment(attribute, by)
648
678
  change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
649
679
  self.class.update_counters(id, attribute => change, touch: touch)
@@ -812,159 +842,159 @@ module ActiveRecord
812
842
  end
813
843
  end
814
844
 
815
- private
816
- def init_internals
817
- super
818
- @_trigger_destroy_callback = @_trigger_update_callback = nil
819
- @previously_new_record = false
820
- end
845
+ private
846
+ def init_internals
847
+ super
848
+ @_trigger_destroy_callback = @_trigger_update_callback = nil
849
+ @previously_new_record = false
850
+ end
821
851
 
822
- def strict_loaded_associations
823
- @association_cache.find_all do |_, assoc|
824
- assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only?
825
- end.map(&:first)
826
- end
852
+ def strict_loaded_associations
853
+ @association_cache.find_all do |_, assoc|
854
+ assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only?
855
+ end.map(&:first)
856
+ end
827
857
 
828
- def _find_record(options)
829
- all_queries = options ? options[:all_queries] : nil
830
- base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations)
858
+ def _find_record(options)
859
+ all_queries = options ? options[:all_queries] : nil
860
+ base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations)
831
861
 
832
- if options && options[:lock]
833
- base.lock(options[:lock]).find_by!(_in_memory_query_constraints_hash)
834
- else
835
- base.find_by!(_in_memory_query_constraints_hash)
862
+ if options && options[:lock]
863
+ base.lock(options[:lock]).find_by!(_in_memory_query_constraints_hash)
864
+ else
865
+ base.find_by!(_in_memory_query_constraints_hash)
866
+ end
836
867
  end
837
- end
838
868
 
839
- def _in_memory_query_constraints_hash
840
- if self.class.query_constraints_list.nil?
841
- { @primary_key => id }
842
- else
843
- self.class.query_constraints_list.index_with do |column_name|
844
- attribute(column_name)
869
+ def _in_memory_query_constraints_hash
870
+ if self.class.query_constraints_list.nil?
871
+ { @primary_key => id }
872
+ else
873
+ self.class.query_constraints_list.index_with do |column_name|
874
+ attribute(column_name)
875
+ end
845
876
  end
846
877
  end
847
- end
848
878
 
849
- def apply_scoping?(options)
850
- !(options && options[:unscoped]) &&
851
- (self.class.default_scopes?(all_queries: true) || self.class.global_current_scope)
852
- end
879
+ def apply_scoping?(options)
880
+ !(options && options[:unscoped]) &&
881
+ (self.class.default_scopes?(all_queries: true) || self.class.global_current_scope)
882
+ end
853
883
 
854
- def _query_constraints_hash
855
- if self.class.query_constraints_list.nil?
856
- { @primary_key => id_in_database }
857
- else
858
- self.class.query_constraints_list.index_with do |column_name|
859
- attribute_in_database(column_name)
884
+ def _query_constraints_hash
885
+ if self.class.query_constraints_list.nil?
886
+ { @primary_key => id_in_database }
887
+ else
888
+ self.class.query_constraints_list.index_with do |column_name|
889
+ attribute_in_database(column_name)
890
+ end
860
891
  end
861
892
  end
862
- end
863
893
 
864
- # A hook to be overridden by association modules.
865
- def destroy_associations
866
- end
867
-
868
- def destroy_row
869
- _delete_row
870
- end
871
-
872
- def _delete_row
873
- self.class._delete_record(_query_constraints_hash)
874
- end
894
+ # A hook to be overridden by association modules.
895
+ def destroy_associations
896
+ end
875
897
 
876
- def _touch_row(attribute_names, time)
877
- time ||= current_time_from_proper_timezone
898
+ def destroy_row
899
+ _delete_row
900
+ end
878
901
 
879
- attribute_names.each do |attr_name|
880
- _write_attribute(attr_name, time)
902
+ def _delete_row
903
+ self.class._delete_record(_query_constraints_hash)
881
904
  end
882
905
 
883
- _update_row(attribute_names, "touch")
884
- end
906
+ def _touch_row(attribute_names, time)
907
+ time ||= current_time_from_proper_timezone
885
908
 
886
- def _update_row(attribute_names, attempted_action = "update")
887
- self.class._update_record(
888
- attributes_with_values(attribute_names),
889
- _query_constraints_hash
890
- )
891
- end
909
+ attribute_names.each do |attr_name|
910
+ _write_attribute(attr_name, time)
911
+ end
892
912
 
893
- def create_or_update(**, &block)
894
- _raise_readonly_record_error if readonly?
895
- return false if destroyed?
896
- result = new_record? ? _create_record(&block) : _update_record(&block)
897
- result != false
898
- end
913
+ _update_row(attribute_names, "touch")
914
+ end
899
915
 
900
- # Updates the associated record with values matching those of the instance attributes.
901
- # Returns the number of affected rows.
902
- def _update_record(attribute_names = self.attribute_names)
903
- attribute_names = attributes_for_update(attribute_names)
916
+ def _update_row(attribute_names, attempted_action = "update")
917
+ self.class._update_record(
918
+ attributes_with_values(attribute_names),
919
+ _query_constraints_hash
920
+ )
921
+ end
904
922
 
905
- if attribute_names.empty?
906
- affected_rows = 0
907
- @_trigger_update_callback = true
908
- else
909
- affected_rows = _update_row(attribute_names)
910
- @_trigger_update_callback = affected_rows == 1
923
+ def create_or_update(**, &block)
924
+ _raise_readonly_record_error if readonly?
925
+ return false if destroyed?
926
+ result = new_record? ? _create_record(&block) : _update_record(&block)
927
+ result != false
911
928
  end
912
929
 
913
- @previously_new_record = false
930
+ # Updates the associated record with values matching those of the instance attributes.
931
+ # Returns the number of affected rows.
932
+ def _update_record(attribute_names = self.attribute_names)
933
+ attribute_names = attributes_for_update(attribute_names)
934
+
935
+ if attribute_names.empty?
936
+ affected_rows = 0
937
+ @_trigger_update_callback = true
938
+ else
939
+ affected_rows = _update_row(attribute_names)
940
+ @_trigger_update_callback = affected_rows == 1
941
+ end
914
942
 
915
- yield(self) if block_given?
943
+ @previously_new_record = false
916
944
 
917
- affected_rows
918
- end
945
+ yield(self) if block_given?
919
946
 
920
- # Creates a record with values matching those of the instance attributes
921
- # and returns its id.
922
- def _create_record(attribute_names = self.attribute_names)
923
- attribute_names = attributes_for_create(attribute_names)
947
+ affected_rows
948
+ end
924
949
 
925
- self.class.with_connection do |connection|
926
- returning_columns = self.class._returning_columns_for_insert(connection)
950
+ # Creates a record with values matching those of the instance attributes
951
+ # and returns its id.
952
+ def _create_record(attribute_names = self.attribute_names)
953
+ attribute_names = attributes_for_create(attribute_names)
927
954
 
928
- returning_values = self.class._insert_record(
929
- connection,
930
- attributes_with_values(attribute_names),
931
- returning_columns
932
- )
955
+ self.class.with_connection do |connection|
956
+ returning_columns = self.class._returning_columns_for_insert(connection)
933
957
 
934
- returning_columns.zip(returning_values).each do |column, value|
935
- _write_attribute(column, value) if !_read_attribute(column)
936
- end if returning_values
937
- end
958
+ returning_values = self.class._insert_record(
959
+ connection,
960
+ attributes_with_values(attribute_names),
961
+ returning_columns
962
+ )
938
963
 
939
- @new_record = false
940
- @previously_new_record = true
964
+ returning_columns.zip(returning_values).each do |column, value|
965
+ _write_attribute(column, type_for_attribute(column).deserialize(value)) if !_read_attribute(column)
966
+ end if returning_values
967
+ end
941
968
 
942
- yield(self) if block_given?
969
+ @new_record = false
970
+ @previously_new_record = true
943
971
 
944
- id
945
- end
972
+ yield(self) if block_given?
946
973
 
947
- def verify_readonly_attribute(name)
948
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
949
- end
974
+ id
975
+ end
950
976
 
951
- def _raise_record_not_destroyed
952
- @_association_destroy_exception ||= nil
953
- key = self.class.primary_key
954
- raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
955
- ensure
956
- @_association_destroy_exception = nil
957
- end
977
+ def verify_readonly_attribute(name)
978
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
979
+ end
958
980
 
959
- def _raise_readonly_record_error
960
- raise ReadOnlyRecord, "#{self.class} is marked as readonly"
961
- end
981
+ def _raise_record_not_destroyed
982
+ @_association_destroy_exception ||= nil
983
+ key = self.class.primary_key
984
+ raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
985
+ ensure
986
+ @_association_destroy_exception = nil
987
+ end
962
988
 
963
- def _raise_record_not_touched_error
964
- raise ActiveRecordError, <<~MSG.squish
965
- Cannot touch on a new or destroyed record object. Consider using
966
- persisted?, new_record?, or destroyed? before touching.
967
- MSG
968
- end
989
+ def _raise_readonly_record_error
990
+ raise ReadOnlyRecord, "#{self.class} is marked as readonly"
991
+ end
992
+
993
+ def _raise_record_not_touched_error
994
+ raise ActiveRecordError, <<~MSG.squish
995
+ Cannot touch on a new or destroyed record object. Consider using
996
+ persisted?, new_record?, or destroyed? before touching.
997
+ MSG
998
+ end
969
999
  end
970
1000
  end