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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1013 -878
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +69 -27
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +2 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +7 -3
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency.rb +3 -2
- data/lib/active_record/associations/preloader/association.rb +3 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +18 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +35 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +74 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +76 -0
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +31 -29
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +56 -13
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +40 -15
- data/lib/active_record/nested_attributes.rb +14 -12
- data/lib/active_record/persistence.rb +163 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +54 -22
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +60 -43
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +73 -43
- data/lib/active_record/relation/merger.rb +12 -10
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +23 -8
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +200 -96
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +127 -87
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +17 -10
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +44 -16
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +53 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +18 -4
- data/lib/active_record/test_fixtures.rb +52 -6
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +56 -32
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +13 -2
- data/lib/active_record/type/serialized.rb +16 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +85 -50
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +4 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +15 -12
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- 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
|
|
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
|
|
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
|
|
125
|
-
# auto-incrementing integer
|
|
126
|
-
#
|
|
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
|
|
287
|
+
# Returns a quoted version of the table name.
|
|
286
288
|
def quoted_table_name
|
|
287
|
-
|
|
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 -
|
|
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
|
|
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[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
317
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
877
|
-
|
|
898
|
+
def destroy_row
|
|
899
|
+
_delete_row
|
|
900
|
+
end
|
|
878
901
|
|
|
879
|
-
|
|
880
|
-
|
|
902
|
+
def _delete_row
|
|
903
|
+
self.class._delete_record(_query_constraints_hash)
|
|
881
904
|
end
|
|
882
905
|
|
|
883
|
-
|
|
884
|
-
|
|
906
|
+
def _touch_row(attribute_names, time)
|
|
907
|
+
time ||= current_time_from_proper_timezone
|
|
885
908
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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
|
-
|
|
894
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
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
|
-
|
|
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
|
-
|
|
943
|
+
@previously_new_record = false
|
|
916
944
|
|
|
917
|
-
|
|
918
|
-
end
|
|
945
|
+
yield(self) if block_given?
|
|
919
946
|
|
|
920
|
-
|
|
921
|
-
|
|
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
|
-
|
|
926
|
-
|
|
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
|
-
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
958
|
+
returning_values = self.class._insert_record(
|
|
959
|
+
connection,
|
|
960
|
+
attributes_with_values(attribute_names),
|
|
961
|
+
returning_columns
|
|
962
|
+
)
|
|
938
963
|
|
|
939
|
-
|
|
940
|
-
|
|
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
|
-
|
|
969
|
+
@new_record = false
|
|
970
|
+
@previously_new_record = true
|
|
943
971
|
|
|
944
|
-
|
|
945
|
-
end
|
|
972
|
+
yield(self) if block_given?
|
|
946
973
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
end
|
|
974
|
+
id
|
|
975
|
+
end
|
|
950
976
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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
|