activerecord 7.1.5 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +515 -2440
- data/README.rdoc +15 -15
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +9 -8
- data/lib/active_record/associations/belongs_to_association.rb +14 -7
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/belongs_to.rb +1 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/collection_association.rb +6 -4
- data/lib/active_record/associations/collection_proxy.rb +14 -1
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +29 -28
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +2 -1
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +33 -16
- data/lib/active_record/attribute_assignment.rb +1 -11
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +1 -1
- data/lib/active_record/attribute_methods/primary_key.rb +23 -55
- data/lib/active_record/attribute_methods/read.rb +4 -16
- data/lib/active_record/attribute_methods/serialization.rb +4 -24
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -10
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +60 -71
- data/lib/active_record/attributes.rb +55 -42
- data/lib/active_record/autosave_association.rb +13 -32
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +24 -107
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +248 -65
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +34 -17
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +159 -74
- data/lib/active_record/connection_adapters/abstract/quoting.rb +65 -91
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/transaction.rb +60 -57
- data/lib/active_record/connection_adapters/abstract_adapter.rb +18 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +43 -48
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -1
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +11 -5
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +5 -23
- data/lib/active_record/connection_adapters/pool_config.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +27 -4
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -13
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -21
- data/lib/active_record/connection_adapters/schema_cache.rb +123 -128
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +10 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +44 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +25 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +107 -75
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +12 -6
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -48
- data/lib/active_record/connection_adapters.rb +121 -0
- data/lib/active_record/connection_handling.rb +56 -41
- data/lib/active_record/core.rb +53 -37
- data/lib/active_record/counter_cache.rb +18 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +15 -4
- data/lib/active_record/database_configurations/hash_config.rb +38 -34
- data/lib/active_record/database_configurations/url_config.rb +20 -1
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +24 -0
- data/lib/active_record/dynamic_matchers.rb +2 -2
- data/lib/active_record/encryption/encryptable_record.rb +2 -2
- data/lib/active_record/encryption/encrypted_attribute_type.rb +22 -2
- data/lib/active_record/encryption/encryptor.rb +17 -2
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption.rb +0 -2
- data/lib/active_record/enum.rb +10 -1
- data/lib/active_record/errors.rb +16 -11
- data/lib/active_record/explain.rb +13 -24
- data/lib/active_record/fixtures.rb +37 -31
- data/lib/active_record/future_result.rb +8 -4
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +4 -2
- data/lib/active_record/insert_all.rb +18 -15
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +7 -6
- data/lib/active_record/log_subscriber.rb +0 -21
- data/lib/active_record/marshalling.rb +1 -4
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +2 -3
- data/lib/active_record/migration/compatibility.rb +5 -3
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +85 -76
- data/lib/active_record/model_schema.rb +28 -68
- data/lib/active_record/nested_attributes.rb +13 -16
- data/lib/active_record/normalization.rb +3 -7
- data/lib/active_record/persistence.rb +30 -352
- data/lib/active_record/query_cache.rb +18 -6
- data/lib/active_record/query_logs.rb +15 -0
- data/lib/active_record/querying.rb +21 -9
- data/lib/active_record/railtie.rb +50 -62
- data/lib/active_record/railties/controller_runtime.rb +13 -4
- data/lib/active_record/railties/databases.rake +41 -44
- data/lib/active_record/reflection.rb +90 -35
- data/lib/active_record/relation/batches/batch_enumerator.rb +15 -2
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +94 -61
- data/lib/active_record/relation/delegation.rb +8 -11
- data/lib/active_record/relation/finder_methods.rb +16 -2
- data/lib/active_record/relation/merger.rb +4 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +3 -3
- data/lib/active_record/relation/query_methods.rb +196 -57
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +2 -18
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +496 -72
- data/lib/active_record/result.rb +31 -44
- data/lib/active_record/runtime_registry.rb +39 -0
- data/lib/active_record/sanitization.rb +24 -19
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +19 -9
- data/lib/active_record/schema_migration.rb +30 -14
- data/lib/active_record/signed_id.rb +11 -1
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/table_metadata.rb +1 -10
- data/lib/active_record/tasks/database_tasks.rb +76 -70
- data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -1
- data/lib/active_record/test_fixtures.rb +81 -91
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +1 -1
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +68 -0
- data/lib/active_record/transactions.rb +43 -14
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/serialized.rb +1 -3
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/associated.rb +9 -3
- data/lib/active_record/validations/uniqueness.rb +14 -10
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +149 -40
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/nodes/binary.rb +0 -6
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +4 -3
- data/lib/arel/nodes/sql_literal.rb +7 -0
- data/lib/arel/nodes.rb +2 -2
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +29 -16
- data/lib/arel.rb +7 -3
- metadata +20 -15
@@ -421,10 +421,15 @@ module ActiveRecord
|
|
421
421
|
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
|
422
422
|
# then the existing record will be marked for destruction.
|
423
423
|
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
|
424
|
-
options = nested_attributes_options[association_name]
|
425
424
|
if attributes.respond_to?(:permitted?)
|
426
425
|
attributes = attributes.to_h
|
427
426
|
end
|
427
|
+
|
428
|
+
unless attributes.is_a?(Hash)
|
429
|
+
raise ArgumentError, "Hash expected for `#{association_name}` attributes, got #{attributes.class.name}"
|
430
|
+
end
|
431
|
+
|
432
|
+
options = nested_attributes_options[association_name]
|
428
433
|
attributes = attributes.with_indifferent_access
|
429
434
|
existing_record = send(association_name)
|
430
435
|
|
@@ -486,7 +491,7 @@ module ActiveRecord
|
|
486
491
|
end
|
487
492
|
|
488
493
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
489
|
-
raise ArgumentError, "Hash or Array expected for
|
494
|
+
raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}"
|
490
495
|
end
|
491
496
|
|
492
497
|
check_record_limit!(options[:limit], attributes_collection)
|
@@ -509,7 +514,7 @@ module ActiveRecord
|
|
509
514
|
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
510
515
|
end
|
511
516
|
|
512
|
-
attributes_collection.
|
517
|
+
records = attributes_collection.map do |attributes|
|
513
518
|
if attributes.respond_to?(:permitted?)
|
514
519
|
attributes = attributes.to_h
|
515
520
|
end
|
@@ -519,12 +524,12 @@ module ActiveRecord
|
|
519
524
|
unless reject_new_record?(association_name, attributes)
|
520
525
|
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
521
526
|
end
|
522
|
-
elsif existing_record =
|
527
|
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s }
|
523
528
|
unless call_reject_if(association_name, attributes)
|
524
529
|
# Make sure we are operating on the actual object which is in the association's
|
525
530
|
# proxy_target array (either by finding it, or adding it if not found)
|
526
531
|
# Take into account that the proxy_target may have changed due to callbacks
|
527
|
-
target_record =
|
532
|
+
target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s }
|
528
533
|
if target_record
|
529
534
|
existing_record = target_record
|
530
535
|
else
|
@@ -532,11 +537,14 @@ module ActiveRecord
|
|
532
537
|
end
|
533
538
|
|
534
539
|
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
540
|
+
existing_record
|
535
541
|
end
|
536
542
|
else
|
537
543
|
raise_nested_attributes_record_not_found!(association_name, attributes["id"])
|
538
544
|
end
|
539
545
|
end
|
546
|
+
|
547
|
+
association.nested_attributes_target = records
|
540
548
|
end
|
541
549
|
|
542
550
|
# Takes in a limit and checks if the attributes_collection has too many
|
@@ -612,16 +620,5 @@ module ActiveRecord
|
|
612
620
|
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
|
613
621
|
model, "id", record_id)
|
614
622
|
end
|
615
|
-
|
616
|
-
def find_record_by_id(records, id)
|
617
|
-
return if records.empty?
|
618
|
-
|
619
|
-
if records.first.class.composite_primary_key?
|
620
|
-
id = Array(id).map(&:to_s)
|
621
|
-
records.find { |record| Array(record.id).map(&:to_s) == id }
|
622
|
-
else
|
623
|
-
records.find { |record| record.id.to_s == id.to_s }
|
624
|
-
end
|
625
|
-
end
|
626
623
|
end
|
627
624
|
end
|
@@ -86,10 +86,8 @@ module ActiveRecord # :nodoc:
|
|
86
86
|
#
|
87
87
|
# User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
|
88
88
|
def normalizes(*names, with:, apply_to_nil: false)
|
89
|
-
names
|
90
|
-
|
91
|
-
NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
|
92
|
-
end
|
89
|
+
decorate_attributes(names) do |name, cast_type|
|
90
|
+
NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
|
93
91
|
end
|
94
92
|
|
95
93
|
self.normalized_attributes += names.map(&:to_sym)
|
@@ -154,9 +152,7 @@ module ActiveRecord # :nodoc:
|
|
154
152
|
[self.class, cast_type, normalizer, normalize_nil?].hash
|
155
153
|
end
|
156
154
|
|
157
|
-
|
158
|
-
Kernel.instance_method(:inspect).bind_call(self)
|
159
|
-
end
|
155
|
+
define_method(:inspect, Kernel.instance_method(:inspect))
|
160
156
|
|
161
157
|
private
|
162
158
|
def normalize(value)
|
@@ -87,282 +87,6 @@ module ActiveRecord
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
-
# Inserts a single record into the database in a single SQL INSERT
|
91
|
-
# statement. It does not instantiate any models nor does it trigger
|
92
|
-
# Active Record callbacks or validations. Though passed values
|
93
|
-
# go through Active Record's type casting and serialization.
|
94
|
-
#
|
95
|
-
# See #insert_all for documentation.
|
96
|
-
def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
97
|
-
insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
98
|
-
end
|
99
|
-
|
100
|
-
# Inserts multiple records into the database in a single SQL INSERT
|
101
|
-
# statement. It does not instantiate any models nor does it trigger
|
102
|
-
# Active Record callbacks or validations. Though passed values
|
103
|
-
# go through Active Record's type casting and serialization.
|
104
|
-
#
|
105
|
-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
106
|
-
# the attributes for a single row and must have the same keys.
|
107
|
-
#
|
108
|
-
# Rows are considered to be unique by every unique index on the table. Any
|
109
|
-
# duplicate rows are skipped.
|
110
|
-
# Override with <tt>:unique_by</tt> (see below).
|
111
|
-
#
|
112
|
-
# Returns an ActiveRecord::Result with its contents based on
|
113
|
-
# <tt>:returning</tt> (see below).
|
114
|
-
#
|
115
|
-
# ==== Options
|
116
|
-
#
|
117
|
-
# [:returning]
|
118
|
-
# (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
|
119
|
-
# inserted records, which by default is the primary key.
|
120
|
-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
121
|
-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
122
|
-
# clause entirely.
|
123
|
-
#
|
124
|
-
# You can also pass an SQL string if you need more control on the return values
|
125
|
-
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
126
|
-
#
|
127
|
-
# [:unique_by]
|
128
|
-
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
129
|
-
# by every unique index on the table. Any duplicate rows are skipped.
|
130
|
-
#
|
131
|
-
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
132
|
-
#
|
133
|
-
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
134
|
-
# row has an existing id, or is not unique by another unique index,
|
135
|
-
# ActiveRecord::RecordNotUnique is raised.
|
136
|
-
#
|
137
|
-
# Unique indexes can be identified by columns or name:
|
138
|
-
#
|
139
|
-
# unique_by: :isbn
|
140
|
-
# unique_by: %i[ author_id name ]
|
141
|
-
# unique_by: :index_books_on_isbn
|
142
|
-
#
|
143
|
-
# [:record_timestamps]
|
144
|
-
# By default, automatic setting of timestamp columns is controlled by
|
145
|
-
# the model's <tt>record_timestamps</tt> config, matching typical
|
146
|
-
# behavior.
|
147
|
-
#
|
148
|
-
# To override this and force automatic setting of timestamp columns one
|
149
|
-
# way or the other, pass <tt>:record_timestamps</tt>:
|
150
|
-
#
|
151
|
-
# record_timestamps: true # Always set timestamps automatically
|
152
|
-
# record_timestamps: false # Never set timestamps automatically
|
153
|
-
#
|
154
|
-
# Because it relies on the index information from the database
|
155
|
-
# <tt>:unique_by</tt> is recommended to be paired with
|
156
|
-
# Active Record's schema_cache.
|
157
|
-
#
|
158
|
-
# ==== Example
|
159
|
-
#
|
160
|
-
# # Insert records and skip inserting any duplicates.
|
161
|
-
# # Here "Eloquent Ruby" is skipped because its id is not unique.
|
162
|
-
#
|
163
|
-
# Book.insert_all([
|
164
|
-
# { id: 1, title: "Rework", author: "David" },
|
165
|
-
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
166
|
-
# ])
|
167
|
-
#
|
168
|
-
# # insert_all works on chained scopes, and you can use create_with
|
169
|
-
# # to set default attributes for all inserted records.
|
170
|
-
#
|
171
|
-
# author.books.create_with(created_at: Time.now).insert_all([
|
172
|
-
# { id: 1, title: "Rework" },
|
173
|
-
# { id: 2, title: "Eloquent Ruby" }
|
174
|
-
# ])
|
175
|
-
def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
176
|
-
InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps).execute
|
177
|
-
end
|
178
|
-
|
179
|
-
# Inserts a single record into the database in a single SQL INSERT
|
180
|
-
# statement. It does not instantiate any models nor does it trigger
|
181
|
-
# Active Record callbacks or validations. Though passed values
|
182
|
-
# go through Active Record's type casting and serialization.
|
183
|
-
#
|
184
|
-
# See #insert_all! for more.
|
185
|
-
def insert!(attributes, returning: nil, record_timestamps: nil)
|
186
|
-
insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps)
|
187
|
-
end
|
188
|
-
|
189
|
-
# Inserts multiple records into the database in a single SQL INSERT
|
190
|
-
# statement. It does not instantiate any models nor does it trigger
|
191
|
-
# Active Record callbacks or validations. Though passed values
|
192
|
-
# go through Active Record's type casting and serialization.
|
193
|
-
#
|
194
|
-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
195
|
-
# the attributes for a single row and must have the same keys.
|
196
|
-
#
|
197
|
-
# Raises ActiveRecord::RecordNotUnique if any rows violate a
|
198
|
-
# unique index on the table. In that case, no rows are inserted.
|
199
|
-
#
|
200
|
-
# To skip duplicate rows, see #insert_all. To replace them, see #upsert_all.
|
201
|
-
#
|
202
|
-
# Returns an ActiveRecord::Result with its contents based on
|
203
|
-
# <tt>:returning</tt> (see below).
|
204
|
-
#
|
205
|
-
# ==== Options
|
206
|
-
#
|
207
|
-
# [:returning]
|
208
|
-
# (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
|
209
|
-
# inserted records, which by default is the primary key.
|
210
|
-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
211
|
-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
212
|
-
# clause entirely.
|
213
|
-
#
|
214
|
-
# You can also pass an SQL string if you need more control on the return values
|
215
|
-
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
216
|
-
#
|
217
|
-
# [:record_timestamps]
|
218
|
-
# By default, automatic setting of timestamp columns is controlled by
|
219
|
-
# the model's <tt>record_timestamps</tt> config, matching typical
|
220
|
-
# behavior.
|
221
|
-
#
|
222
|
-
# To override this and force automatic setting of timestamp columns one
|
223
|
-
# way or the other, pass <tt>:record_timestamps</tt>:
|
224
|
-
#
|
225
|
-
# record_timestamps: true # Always set timestamps automatically
|
226
|
-
# record_timestamps: false # Never set timestamps automatically
|
227
|
-
#
|
228
|
-
# ==== Examples
|
229
|
-
#
|
230
|
-
# # Insert multiple records
|
231
|
-
# Book.insert_all!([
|
232
|
-
# { title: "Rework", author: "David" },
|
233
|
-
# { title: "Eloquent Ruby", author: "Russ" }
|
234
|
-
# ])
|
235
|
-
#
|
236
|
-
# # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
|
237
|
-
# # does not have a unique id.
|
238
|
-
# Book.insert_all!([
|
239
|
-
# { id: 1, title: "Rework", author: "David" },
|
240
|
-
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
241
|
-
# ])
|
242
|
-
def insert_all!(attributes, returning: nil, record_timestamps: nil)
|
243
|
-
InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps).execute
|
244
|
-
end
|
245
|
-
|
246
|
-
# Updates or inserts (upserts) a single record into the database in a
|
247
|
-
# single SQL INSERT statement. It does not instantiate any models nor does
|
248
|
-
# it trigger Active Record callbacks or validations. Though passed values
|
249
|
-
# go through Active Record's type casting and serialization.
|
250
|
-
#
|
251
|
-
# See #upsert_all for documentation.
|
252
|
-
def upsert(attributes, **kwargs)
|
253
|
-
upsert_all([ attributes ], **kwargs)
|
254
|
-
end
|
255
|
-
|
256
|
-
# Updates or inserts (upserts) multiple records into the database in a
|
257
|
-
# single SQL INSERT statement. It does not instantiate any models nor does
|
258
|
-
# it trigger Active Record callbacks or validations. Though passed values
|
259
|
-
# go through Active Record's type casting and serialization.
|
260
|
-
#
|
261
|
-
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
262
|
-
# the attributes for a single row and must have the same keys.
|
263
|
-
#
|
264
|
-
# Returns an ActiveRecord::Result with its contents based on
|
265
|
-
# <tt>:returning</tt> (see below).
|
266
|
-
#
|
267
|
-
# By default, +upsert_all+ will update all the columns that can be updated when
|
268
|
-
# there is a conflict. These are all the columns except primary keys, read-only
|
269
|
-
# columns, and columns covered by the optional +unique_by+.
|
270
|
-
#
|
271
|
-
# ==== Options
|
272
|
-
#
|
273
|
-
# [:returning]
|
274
|
-
# (PostgreSQL and SQLite3 only) An array of attributes to return for all successfully
|
275
|
-
# inserted records, which by default is the primary key.
|
276
|
-
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
277
|
-
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
278
|
-
# clause entirely.
|
279
|
-
#
|
280
|
-
# You can also pass an SQL string if you need more control on the return values
|
281
|
-
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
282
|
-
#
|
283
|
-
# [:unique_by]
|
284
|
-
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
285
|
-
# by every unique index on the table. Any duplicate rows are skipped.
|
286
|
-
#
|
287
|
-
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
288
|
-
#
|
289
|
-
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
290
|
-
# row has an existing id, or is not unique by another unique index,
|
291
|
-
# ActiveRecord::RecordNotUnique is raised.
|
292
|
-
#
|
293
|
-
# Unique indexes can be identified by columns or name:
|
294
|
-
#
|
295
|
-
# unique_by: :isbn
|
296
|
-
# unique_by: %i[ author_id name ]
|
297
|
-
# unique_by: :index_books_on_isbn
|
298
|
-
#
|
299
|
-
# Because it relies on the index information from the database
|
300
|
-
# <tt>:unique_by</tt> is recommended to be paired with
|
301
|
-
# Active Record's schema_cache.
|
302
|
-
#
|
303
|
-
# [:on_duplicate]
|
304
|
-
# Configure the SQL update sentence that will be used in case of conflict.
|
305
|
-
#
|
306
|
-
# NOTE: If you use this option you must provide all the columns you want to update
|
307
|
-
# by yourself.
|
308
|
-
#
|
309
|
-
# Example:
|
310
|
-
#
|
311
|
-
# Commodity.upsert_all(
|
312
|
-
# [
|
313
|
-
# { id: 2, name: "Copper", price: 4.84 },
|
314
|
-
# { id: 4, name: "Gold", price: 1380.87 },
|
315
|
-
# { id: 6, name: "Aluminium", price: 0.35 }
|
316
|
-
# ],
|
317
|
-
# on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)")
|
318
|
-
# )
|
319
|
-
#
|
320
|
-
# See the related +:update_only+ option. Both options can't be used at the same time.
|
321
|
-
#
|
322
|
-
# [:update_only]
|
323
|
-
# Provide a list of column names that will be updated in case of conflict. If not provided,
|
324
|
-
# +upsert_all+ will update all the columns that can be updated. These are all the columns
|
325
|
-
# except primary keys, read-only columns, and columns covered by the optional +unique_by+
|
326
|
-
#
|
327
|
-
# Example:
|
328
|
-
#
|
329
|
-
# Commodity.upsert_all(
|
330
|
-
# [
|
331
|
-
# { id: 2, name: "Copper", price: 4.84 },
|
332
|
-
# { id: 4, name: "Gold", price: 1380.87 },
|
333
|
-
# { id: 6, name: "Aluminium", price: 0.35 }
|
334
|
-
# ],
|
335
|
-
# update_only: [:price] # Only prices will be updated
|
336
|
-
# )
|
337
|
-
#
|
338
|
-
# See the related +:on_duplicate+ option. Both options can't be used at the same time.
|
339
|
-
#
|
340
|
-
# [:record_timestamps]
|
341
|
-
# By default, automatic setting of timestamp columns is controlled by
|
342
|
-
# the model's <tt>record_timestamps</tt> config, matching typical
|
343
|
-
# behavior.
|
344
|
-
#
|
345
|
-
# To override this and force automatic setting of timestamp columns one
|
346
|
-
# way or the other, pass <tt>:record_timestamps</tt>:
|
347
|
-
#
|
348
|
-
# record_timestamps: true # Always set timestamps automatically
|
349
|
-
# record_timestamps: false # Never set timestamps automatically
|
350
|
-
#
|
351
|
-
# ==== Examples
|
352
|
-
#
|
353
|
-
# # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
|
354
|
-
# # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
|
355
|
-
#
|
356
|
-
# Book.upsert_all([
|
357
|
-
# { title: "Rework", author: "David", isbn: "1" },
|
358
|
-
# { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
|
359
|
-
# ], unique_by: :isbn)
|
360
|
-
#
|
361
|
-
# Book.find_by(isbn: "1").title # => "Eloquent Ruby"
|
362
|
-
def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
363
|
-
InsertAll.new(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps).execute
|
364
|
-
end
|
365
|
-
|
366
90
|
# Given an attributes hash, +instantiate+ returns a new instance of
|
367
91
|
# the appropriate class. Accepts only keys as strings.
|
368
92
|
#
|
@@ -511,62 +235,7 @@ module ActiveRecord
|
|
511
235
|
@composite_query_constraints_list ||= query_constraints_list || Array(primary_key)
|
512
236
|
end
|
513
237
|
|
514
|
-
|
515
|
-
# therefore all callbacks and filters are fired off before the object is deleted. This method is
|
516
|
-
# less efficient than #delete but allows cleanup methods and other actions to be run.
|
517
|
-
#
|
518
|
-
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
519
|
-
# from the attributes, and then calls destroy on it.
|
520
|
-
#
|
521
|
-
# ==== Parameters
|
522
|
-
#
|
523
|
-
# * +id+ - This should be the id or an array of ids to be destroyed.
|
524
|
-
#
|
525
|
-
# ==== Examples
|
526
|
-
#
|
527
|
-
# # Destroy a single object
|
528
|
-
# Todo.destroy(1)
|
529
|
-
#
|
530
|
-
# # Destroy multiple objects
|
531
|
-
# todos = [1,2,3]
|
532
|
-
# Todo.destroy(todos)
|
533
|
-
def destroy(id)
|
534
|
-
multiple_ids = if composite_primary_key?
|
535
|
-
id.first.is_a?(Array)
|
536
|
-
else
|
537
|
-
id.is_a?(Array)
|
538
|
-
end
|
539
|
-
|
540
|
-
if multiple_ids
|
541
|
-
find(id).each(&:destroy)
|
542
|
-
else
|
543
|
-
find(id).destroy
|
544
|
-
end
|
545
|
-
end
|
546
|
-
|
547
|
-
# Deletes the row with a primary key matching the +id+ argument, using an
|
548
|
-
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
549
|
-
# Record objects are not instantiated, so the object's callbacks are not
|
550
|
-
# executed, including any <tt>:dependent</tt> association options.
|
551
|
-
#
|
552
|
-
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
553
|
-
#
|
554
|
-
# Note: Although it is often much faster than the alternative, #destroy,
|
555
|
-
# skipping callbacks might bypass business logic in your application
|
556
|
-
# that ensures referential integrity or performs other essential jobs.
|
557
|
-
#
|
558
|
-
# ==== Examples
|
559
|
-
#
|
560
|
-
# # Delete a single row
|
561
|
-
# Todo.delete(1)
|
562
|
-
#
|
563
|
-
# # Delete multiple rows
|
564
|
-
# Todo.delete([2,3,4])
|
565
|
-
def delete(id_or_array)
|
566
|
-
delete_by(primary_key => id_or_array)
|
567
|
-
end
|
568
|
-
|
569
|
-
def _insert_record(values, returning) # :nodoc:
|
238
|
+
def _insert_record(connection, values, returning) # :nodoc:
|
570
239
|
primary_key = self.primary_key
|
571
240
|
primary_key_value = nil
|
572
241
|
|
@@ -579,16 +248,18 @@ module ActiveRecord
|
|
579
248
|
|
580
249
|
im = Arel::InsertManager.new(arel_table)
|
581
250
|
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
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
|
587
257
|
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
258
|
+
connection.insert(
|
259
|
+
im, "#{self} Create", primary_key || false, primary_key_value,
|
260
|
+
returning: returning
|
261
|
+
)
|
262
|
+
end
|
592
263
|
end
|
593
264
|
|
594
265
|
def _update_record(values, constraints) # :nodoc:
|
@@ -605,7 +276,9 @@ module ActiveRecord
|
|
605
276
|
um.set(values.transform_keys { |name| arel_table[name] })
|
606
277
|
um.wheres = constraints
|
607
278
|
|
608
|
-
|
279
|
+
with_connection do |c|
|
280
|
+
c.update(um, "#{self} Update")
|
281
|
+
end
|
609
282
|
end
|
610
283
|
|
611
284
|
def _delete_record(constraints) # :nodoc:
|
@@ -621,7 +294,9 @@ module ActiveRecord
|
|
621
294
|
dm = Arel::DeleteManager.new(arel_table)
|
622
295
|
dm.wheres = constraints
|
623
296
|
|
624
|
-
|
297
|
+
with_connection do |c|
|
298
|
+
c.delete(dm, "#{self} Destroy")
|
299
|
+
end
|
625
300
|
end
|
626
301
|
|
627
302
|
private
|
@@ -1067,7 +742,7 @@ module ActiveRecord
|
|
1067
742
|
# end
|
1068
743
|
#
|
1069
744
|
def reload(options = nil)
|
1070
|
-
self.class.
|
745
|
+
self.class.connection_pool.clear_query_cache
|
1071
746
|
|
1072
747
|
fresh_object = if apply_scoping?(options)
|
1073
748
|
_find_record((options || {}).merge(all_queries: true))
|
@@ -1247,16 +922,19 @@ module ActiveRecord
|
|
1247
922
|
def _create_record(attribute_names = self.attribute_names)
|
1248
923
|
attribute_names = attributes_for_create(attribute_names)
|
1249
924
|
|
1250
|
-
|
925
|
+
self.class.with_connection do |connection|
|
926
|
+
returning_columns = self.class._returning_columns_for_insert(connection)
|
1251
927
|
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
928
|
+
returning_values = self.class._insert_record(
|
929
|
+
connection,
|
930
|
+
attributes_with_values(attribute_names),
|
931
|
+
returning_columns
|
932
|
+
)
|
1256
933
|
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
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
|
1260
938
|
|
1261
939
|
@new_record = false
|
1262
940
|
@previously_new_record = true
|
@@ -8,7 +8,13 @@ module ActiveRecord
|
|
8
8
|
# If it's not, it will execute the given block.
|
9
9
|
def cache(&block)
|
10
10
|
if connected? || !configurations.empty?
|
11
|
-
|
11
|
+
pool = connection_pool
|
12
|
+
was_enabled = pool.query_cache_enabled
|
13
|
+
begin
|
14
|
+
pool.enable_query_cache(&block)
|
15
|
+
ensure
|
16
|
+
pool.clear_query_cache unless was_enabled
|
17
|
+
end
|
12
18
|
else
|
13
19
|
yield
|
14
20
|
end
|
@@ -16,9 +22,12 @@ module ActiveRecord
|
|
16
22
|
|
17
23
|
# Disable the query cache within the block if Active Record is configured.
|
18
24
|
# If it's not, it will execute the given block.
|
19
|
-
|
25
|
+
#
|
26
|
+
# Set <tt>dirties: false</tt> to prevent query caches on all connections from being cleared by write operations.
|
27
|
+
# (By default, write operations dirty all connections' query caches in case they are replicas whose cache would now be outdated.)
|
28
|
+
def uncached(dirties: true, &block)
|
20
29
|
if connected? || !configurations.empty?
|
21
|
-
|
30
|
+
connection_pool.disable_query_cache(dirties: dirties, &block)
|
22
31
|
else
|
23
32
|
yield
|
24
33
|
end
|
@@ -26,14 +35,17 @@ module ActiveRecord
|
|
26
35
|
end
|
27
36
|
|
28
37
|
def self.run
|
29
|
-
ActiveRecord::Base.connection_handler.each_connection_pool.reject
|
38
|
+
ActiveRecord::Base.connection_handler.each_connection_pool.reject(&:query_cache_enabled).each(&:enable_query_cache!)
|
30
39
|
end
|
31
40
|
|
32
41
|
def self.complete(pools)
|
33
|
-
pools.each
|
42
|
+
pools.each do |pool|
|
43
|
+
pool.disable_query_cache!
|
44
|
+
pool.clear_query_cache
|
45
|
+
end
|
34
46
|
|
35
47
|
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
36
|
-
pool.release_connection if pool.active_connection? && !pool.
|
48
|
+
pool.release_connection if pool.active_connection? && !pool.lease_connection.transaction_open?
|
37
49
|
end
|
38
50
|
end
|
39
51
|
|
@@ -26,6 +26,7 @@ module ActiveRecord
|
|
26
26
|
# * +socket+
|
27
27
|
# * +db_host+
|
28
28
|
# * +database+
|
29
|
+
# * +source_location+
|
29
30
|
#
|
30
31
|
# Action Controller adds default tags when loaded:
|
31
32
|
#
|
@@ -108,6 +109,20 @@ module ActiveRecord
|
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
112
|
+
if Thread.respond_to?(:each_caller_location)
|
113
|
+
def query_source_location # :nodoc:
|
114
|
+
Thread.each_caller_location do |location|
|
115
|
+
frame = LogSubscriber.backtrace_cleaner.clean_frame(location.path)
|
116
|
+
return frame if frame
|
117
|
+
end
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
else
|
121
|
+
def query_source_location # :nodoc:
|
122
|
+
LogSubscriber.backtrace_cleaner.clean(caller_locations(1).each).first
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
111
126
|
ActiveSupport::ExecutionContext.after_change { ActiveRecord::QueryLogs.clear_cache }
|
112
127
|
|
113
128
|
private
|