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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +564 -753
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/belongs_to_association.rb +18 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- 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 +10 -8
- data/lib/active_record/associations/collection_proxy.rb +22 -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/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- 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 +17 -4
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods.rb +24 -19
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +91 -39
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/coders/json.rb +14 -5
- 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 +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
- 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 +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
- 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 +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- 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/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 +28 -45
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -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 +13 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +37 -10
- data/lib/active_record/core.rb +61 -25
- data/lib/active_record/counter_cache.rb +34 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +19 -19
- 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 +9 -9
- data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
- data/lib/active_record/encryption/encryptor.rb +49 -28
- 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 +46 -42
- data/lib/active_record/errors.rb +36 -12
- 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 +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -4
- data/lib/active_record/future_result.rb +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- 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 +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +44 -11
- 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 +50 -43
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +104 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +12 -12
- data/lib/active_record/railtie.rb +37 -32
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -37
- 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 +53 -21
- 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 +80 -63
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +54 -37
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -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 +22 -7
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +156 -95
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +122 -80
- 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_dumper.rb +47 -22
- 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/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +47 -18
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -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 +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- 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 +39 -16
- 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 +15 -2
- data/lib/active_record/type/serialized.rb +11 -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 +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -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/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/table.rb +3 -7
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +6 -22
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +17 -17
- 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
|
|
@@ -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
|
|
@@ -387,6 +387,8 @@ module ActiveRecord
|
|
|
387
387
|
generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
|
|
388
388
|
silence_redefinition_of_method :#{association_name}_attributes=
|
|
389
389
|
def #{association_name}_attributes=(attributes)
|
|
390
|
+
association = association(:#{association_name})
|
|
391
|
+
deprecated_associations_api_guard(association, __method__)
|
|
390
392
|
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes)
|
|
391
393
|
end
|
|
392
394
|
eoruby
|
|
@@ -524,12 +526,12 @@ module ActiveRecord
|
|
|
524
526
|
unless reject_new_record?(association_name, attributes)
|
|
525
527
|
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
|
526
528
|
end
|
|
527
|
-
elsif existing_record = find_record_by_id(existing_records, attributes["id"])
|
|
529
|
+
elsif existing_record = find_record_by_id(association.klass, existing_records, attributes["id"])
|
|
528
530
|
unless call_reject_if(association_name, attributes)
|
|
529
531
|
# Make sure we are operating on the actual object which is in the association's
|
|
530
532
|
# proxy_target array (either by finding it, or adding it if not found)
|
|
531
533
|
# Take into account that the proxy_target may have changed due to callbacks
|
|
532
|
-
target_record = find_record_by_id(association.target, attributes["id"])
|
|
534
|
+
target_record = find_record_by_id(association.klass, association.target, attributes["id"])
|
|
533
535
|
if target_record
|
|
534
536
|
existing_record = target_record
|
|
535
537
|
else
|
|
@@ -621,10 +623,8 @@ module ActiveRecord
|
|
|
621
623
|
model, "id", record_id)
|
|
622
624
|
end
|
|
623
625
|
|
|
624
|
-
def find_record_by_id(records, id)
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if records.first.class.composite_primary_key?
|
|
626
|
+
def find_record_by_id(klass, records, id)
|
|
627
|
+
if klass.composite_primary_key?
|
|
628
628
|
id = Array(id).map(&:to_s)
|
|
629
629
|
records.find { |record| Array(record.id).map(&:to_s) == id }
|
|
630
630
|
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,6 +492,7 @@ 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?)
|
|
498
497
|
becoming.errors.copy!(errors)
|
|
499
498
|
end
|
|
@@ -583,8 +582,8 @@ module ActiveRecord
|
|
|
583
582
|
end
|
|
584
583
|
|
|
585
584
|
# Equivalent to <code>update_columns(name => value)</code>.
|
|
586
|
-
def update_column(name, value)
|
|
587
|
-
update_columns(name => value)
|
|
585
|
+
def update_column(name, value, touch: nil)
|
|
586
|
+
update_columns(name => value, touch: touch)
|
|
588
587
|
end
|
|
589
588
|
|
|
590
589
|
# Updates the attributes directly in the database issuing an UPDATE SQL
|
|
@@ -598,11 +597,25 @@ module ActiveRecord
|
|
|
598
597
|
#
|
|
599
598
|
# * \Validations are skipped.
|
|
600
599
|
# * \Callbacks are skipped.
|
|
601
|
-
# * +updated_at+/+updated_on+ are
|
|
600
|
+
# * +updated_at+/+updated_on+ are updated if the +touch+ option is set to +true+.
|
|
602
601
|
# * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
|
|
603
602
|
#
|
|
604
603
|
# This method raises an ActiveRecord::ActiveRecordError when called on new
|
|
605
604
|
# objects, or when at least one of the attributes is marked as readonly.
|
|
605
|
+
#
|
|
606
|
+
# ==== Parameters
|
|
607
|
+
#
|
|
608
|
+
# * <tt>:touch</tt> option - Touch the timestamp columns when updating.
|
|
609
|
+
# * If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
|
610
|
+
#
|
|
611
|
+
# ==== Examples
|
|
612
|
+
#
|
|
613
|
+
# # Update a single attribute.
|
|
614
|
+
# user.update_columns(last_request_at: Time.current)
|
|
615
|
+
#
|
|
616
|
+
# # Update with touch option.
|
|
617
|
+
# user.update_columns(last_request_at: Time.current, touch: true)
|
|
618
|
+
|
|
606
619
|
def update_columns(attributes)
|
|
607
620
|
raise ActiveRecordError, "cannot update a new record" if new_record?
|
|
608
621
|
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
|
|
@@ -614,6 +627,15 @@ module ActiveRecord
|
|
|
614
627
|
verify_readonly_attribute(name) || name
|
|
615
628
|
end
|
|
616
629
|
|
|
630
|
+
touch = attributes.delete("touch")
|
|
631
|
+
if touch
|
|
632
|
+
names = touch if touch != true
|
|
633
|
+
names = Array.wrap(names)
|
|
634
|
+
options = names.extract_options!
|
|
635
|
+
touch_updates = self.class.touch_attributes_with_time(*names, **options)
|
|
636
|
+
attributes.with_defaults!(touch_updates) unless touch_updates.empty?
|
|
637
|
+
end
|
|
638
|
+
|
|
617
639
|
update_constraints = _query_constraints_hash
|
|
618
640
|
attributes = attributes.each_with_object({}) do |(k, v), h|
|
|
619
641
|
h[k] = @attributes.write_cast_value(k, v)
|
|
@@ -642,8 +664,15 @@ module ActiveRecord
|
|
|
642
664
|
# This means that any other modified attributes will still be dirty.
|
|
643
665
|
# Validations and callbacks are skipped. Supports the +touch+ option from
|
|
644
666
|
# +update_counters+, see that for more.
|
|
667
|
+
#
|
|
668
|
+
# This method raises an ActiveRecord::ActiveRecordError when called on new
|
|
669
|
+
# objects, or when at least one of the attributes is marked as readonly.
|
|
670
|
+
#
|
|
645
671
|
# Returns +self+.
|
|
646
672
|
def increment!(attribute, by = 1, touch: nil)
|
|
673
|
+
raise ActiveRecordError, "cannot update a new record" if new_record?
|
|
674
|
+
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
|
|
675
|
+
|
|
647
676
|
increment(attribute, by)
|
|
648
677
|
change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
|
|
649
678
|
self.class.update_counters(id, attribute => change, touch: touch)
|
|
@@ -812,159 +841,159 @@ module ActiveRecord
|
|
|
812
841
|
end
|
|
813
842
|
end
|
|
814
843
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
844
|
+
private
|
|
845
|
+
def init_internals
|
|
846
|
+
super
|
|
847
|
+
@_trigger_destroy_callback = @_trigger_update_callback = nil
|
|
848
|
+
@previously_new_record = false
|
|
849
|
+
end
|
|
821
850
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
851
|
+
def strict_loaded_associations
|
|
852
|
+
@association_cache.find_all do |_, assoc|
|
|
853
|
+
assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only?
|
|
854
|
+
end.map(&:first)
|
|
855
|
+
end
|
|
827
856
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
857
|
+
def _find_record(options)
|
|
858
|
+
all_queries = options ? options[:all_queries] : nil
|
|
859
|
+
base = self.class.all(all_queries: all_queries).preload(strict_loaded_associations)
|
|
831
860
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
861
|
+
if options && options[:lock]
|
|
862
|
+
base.lock(options[:lock]).find_by!(_in_memory_query_constraints_hash)
|
|
863
|
+
else
|
|
864
|
+
base.find_by!(_in_memory_query_constraints_hash)
|
|
865
|
+
end
|
|
836
866
|
end
|
|
837
|
-
end
|
|
838
867
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
868
|
+
def _in_memory_query_constraints_hash
|
|
869
|
+
if self.class.query_constraints_list.nil?
|
|
870
|
+
{ @primary_key => id }
|
|
871
|
+
else
|
|
872
|
+
self.class.query_constraints_list.index_with do |column_name|
|
|
873
|
+
attribute(column_name)
|
|
874
|
+
end
|
|
845
875
|
end
|
|
846
876
|
end
|
|
847
|
-
end
|
|
848
877
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
878
|
+
def apply_scoping?(options)
|
|
879
|
+
!(options && options[:unscoped]) &&
|
|
880
|
+
(self.class.default_scopes?(all_queries: true) || self.class.global_current_scope)
|
|
881
|
+
end
|
|
853
882
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
883
|
+
def _query_constraints_hash
|
|
884
|
+
if self.class.query_constraints_list.nil?
|
|
885
|
+
{ @primary_key => id_in_database }
|
|
886
|
+
else
|
|
887
|
+
self.class.query_constraints_list.index_with do |column_name|
|
|
888
|
+
attribute_in_database(column_name)
|
|
889
|
+
end
|
|
860
890
|
end
|
|
861
891
|
end
|
|
862
|
-
end
|
|
863
892
|
|
|
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
|
|
893
|
+
# A hook to be overridden by association modules.
|
|
894
|
+
def destroy_associations
|
|
895
|
+
end
|
|
875
896
|
|
|
876
|
-
|
|
877
|
-
|
|
897
|
+
def destroy_row
|
|
898
|
+
_delete_row
|
|
899
|
+
end
|
|
878
900
|
|
|
879
|
-
|
|
880
|
-
|
|
901
|
+
def _delete_row
|
|
902
|
+
self.class._delete_record(_query_constraints_hash)
|
|
881
903
|
end
|
|
882
904
|
|
|
883
|
-
|
|
884
|
-
|
|
905
|
+
def _touch_row(attribute_names, time)
|
|
906
|
+
time ||= current_time_from_proper_timezone
|
|
885
907
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
_query_constraints_hash
|
|
890
|
-
)
|
|
891
|
-
end
|
|
908
|
+
attribute_names.each do |attr_name|
|
|
909
|
+
_write_attribute(attr_name, time)
|
|
910
|
+
end
|
|
892
911
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
return false if destroyed?
|
|
896
|
-
result = new_record? ? _create_record(&block) : _update_record(&block)
|
|
897
|
-
result != false
|
|
898
|
-
end
|
|
912
|
+
_update_row(attribute_names, "touch")
|
|
913
|
+
end
|
|
899
914
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
915
|
+
def _update_row(attribute_names, attempted_action = "update")
|
|
916
|
+
self.class._update_record(
|
|
917
|
+
attributes_with_values(attribute_names),
|
|
918
|
+
_query_constraints_hash
|
|
919
|
+
)
|
|
920
|
+
end
|
|
904
921
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
@_trigger_update_callback = affected_rows == 1
|
|
922
|
+
def create_or_update(**, &block)
|
|
923
|
+
_raise_readonly_record_error if readonly?
|
|
924
|
+
return false if destroyed?
|
|
925
|
+
result = new_record? ? _create_record(&block) : _update_record(&block)
|
|
926
|
+
result != false
|
|
911
927
|
end
|
|
912
928
|
|
|
913
|
-
|
|
929
|
+
# Updates the associated record with values matching those of the instance attributes.
|
|
930
|
+
# Returns the number of affected rows.
|
|
931
|
+
def _update_record(attribute_names = self.attribute_names)
|
|
932
|
+
attribute_names = attributes_for_update(attribute_names)
|
|
933
|
+
|
|
934
|
+
if attribute_names.empty?
|
|
935
|
+
affected_rows = 0
|
|
936
|
+
@_trigger_update_callback = true
|
|
937
|
+
else
|
|
938
|
+
affected_rows = _update_row(attribute_names)
|
|
939
|
+
@_trigger_update_callback = affected_rows == 1
|
|
940
|
+
end
|
|
914
941
|
|
|
915
|
-
|
|
942
|
+
@previously_new_record = false
|
|
916
943
|
|
|
917
|
-
|
|
918
|
-
end
|
|
944
|
+
yield(self) if block_given?
|
|
919
945
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
def _create_record(attribute_names = self.attribute_names)
|
|
923
|
-
attribute_names = attributes_for_create(attribute_names)
|
|
946
|
+
affected_rows
|
|
947
|
+
end
|
|
924
948
|
|
|
925
|
-
|
|
926
|
-
|
|
949
|
+
# Creates a record with values matching those of the instance attributes
|
|
950
|
+
# and returns its id.
|
|
951
|
+
def _create_record(attribute_names = self.attribute_names)
|
|
952
|
+
attribute_names = attributes_for_create(attribute_names)
|
|
927
953
|
|
|
928
|
-
|
|
929
|
-
connection
|
|
930
|
-
attributes_with_values(attribute_names),
|
|
931
|
-
returning_columns
|
|
932
|
-
)
|
|
954
|
+
self.class.with_connection do |connection|
|
|
955
|
+
returning_columns = self.class._returning_columns_for_insert(connection)
|
|
933
956
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
957
|
+
returning_values = self.class._insert_record(
|
|
958
|
+
connection,
|
|
959
|
+
attributes_with_values(attribute_names),
|
|
960
|
+
returning_columns
|
|
961
|
+
)
|
|
938
962
|
|
|
939
|
-
|
|
940
|
-
|
|
963
|
+
returning_columns.zip(returning_values).each do |column, value|
|
|
964
|
+
_write_attribute(column, type_for_attribute(column).deserialize(value)) if !_read_attribute(column)
|
|
965
|
+
end if returning_values
|
|
966
|
+
end
|
|
941
967
|
|
|
942
|
-
|
|
968
|
+
@new_record = false
|
|
969
|
+
@previously_new_record = true
|
|
943
970
|
|
|
944
|
-
|
|
945
|
-
end
|
|
971
|
+
yield(self) if block_given?
|
|
946
972
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
end
|
|
973
|
+
id
|
|
974
|
+
end
|
|
950
975
|
|
|
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
|
|
976
|
+
def verify_readonly_attribute(name)
|
|
977
|
+
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
|
|
978
|
+
end
|
|
958
979
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
980
|
+
def _raise_record_not_destroyed
|
|
981
|
+
@_association_destroy_exception ||= nil
|
|
982
|
+
key = self.class.primary_key
|
|
983
|
+
raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{id}", self)
|
|
984
|
+
ensure
|
|
985
|
+
@_association_destroy_exception = nil
|
|
986
|
+
end
|
|
962
987
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
988
|
+
def _raise_readonly_record_error
|
|
989
|
+
raise ReadOnlyRecord, "#{self.class} is marked as readonly"
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
def _raise_record_not_touched_error
|
|
993
|
+
raise ActiveRecordError, <<~MSG.squish
|
|
994
|
+
Cannot touch on a new or destroyed record object. Consider using
|
|
995
|
+
persisted?, new_record?, or destroyed? before touching.
|
|
996
|
+
MSG
|
|
997
|
+
end
|
|
969
998
|
end
|
|
970
999
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# = Active Record Query Cache
|
|
5
5
|
class QueryCache
|
|
6
|
+
# ActiveRecord::Base extends this module, so these methods are available in models.
|
|
6
7
|
module ClassMethods
|
|
7
8
|
# Enable the query cache within the block if Active Record is configured.
|
|
8
9
|
# If it's not, it will execute the given block.
|
|
@@ -20,11 +21,15 @@ module ActiveRecord
|
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
24
|
+
# Runs the block with the query cache disabled.
|
|
25
|
+
#
|
|
26
|
+
# If the query cache was enabled before the block was executed, it is
|
|
27
|
+
# enabled again after it.
|
|
25
28
|
#
|
|
26
|
-
# Set <tt>dirties: false</tt> to prevent query caches on all connections
|
|
27
|
-
# (By default, write operations
|
|
29
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections
|
|
30
|
+
# from being cleared by write operations. (By default, write operations
|
|
31
|
+
# dirty all connections' query caches in case they are replicas whose
|
|
32
|
+
# cache would now be outdated.)
|
|
28
33
|
def uncached(dirties: true, &block)
|
|
29
34
|
if connected? || !configurations.empty?
|
|
30
35
|
connection_pool.disable_query_cache(dirties: dirties, &block)
|
|
@@ -34,22 +39,24 @@ module ActiveRecord
|
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
module ExecutorHooks # :nodoc:
|
|
43
|
+
def self.run
|
|
44
|
+
ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each do |pool|
|
|
45
|
+
next if pool.db_config&.query_cache == false
|
|
46
|
+
pool.enable_query_cache!
|
|
47
|
+
end
|
|
41
48
|
end
|
|
42
|
-
end
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
def self.complete(pools)
|
|
51
|
+
pools.each do |pool|
|
|
52
|
+
pool.disable_query_cache!
|
|
53
|
+
pool.clear_query_cache
|
|
54
|
+
end
|
|
48
55
|
end
|
|
49
56
|
end
|
|
50
57
|
|
|
51
|
-
def self.install_executor_hooks(executor = ActiveSupport::Executor)
|
|
52
|
-
executor.register_hook(
|
|
58
|
+
def self.install_executor_hooks(executor = ActiveSupport::Executor) # :nodoc:
|
|
59
|
+
executor.register_hook(ExecutorHooks)
|
|
53
60
|
end
|
|
54
61
|
end
|
|
55
62
|
end
|