activerecord 7.1.5.2 → 8.1.2.1
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 +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- 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/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- 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 +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- 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 +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- 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 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- 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 +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- 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/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- 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 +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- 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 +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- 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 +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- 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 +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -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 +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- 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 +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- 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 +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- 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 +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- 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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -3,6 +3,54 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
# = Active Record \Relation
|
|
5
5
|
class Relation
|
|
6
|
+
class ExplainProxy # :nodoc:
|
|
7
|
+
def initialize(relation, options)
|
|
8
|
+
@relation = relation
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def inspect
|
|
13
|
+
exec_explain { @relation.send(:exec_queries) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def average(column_name)
|
|
17
|
+
exec_explain { @relation.average(column_name) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def count(column_name = nil)
|
|
21
|
+
exec_explain { @relation.count(column_name) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def first(limit = nil)
|
|
25
|
+
exec_explain { @relation.first(limit) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def last(limit = nil)
|
|
29
|
+
exec_explain { @relation.last(limit) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def maximum(column_name)
|
|
33
|
+
exec_explain { @relation.maximum(column_name) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def minimum(column_name)
|
|
37
|
+
exec_explain { @relation.minimum(column_name) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def pluck(*column_names)
|
|
41
|
+
exec_explain { @relation.pluck(*column_names) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sum(identity_or_column = nil)
|
|
45
|
+
exec_explain { @relation.sum(identity_or_column) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def exec_explain(&block)
|
|
50
|
+
@relation.exec_explain(@relation.collecting_queries_for_explain { block.call }, @options)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
6
54
|
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
|
|
7
55
|
:order, :joins, :left_outer_joins, :references,
|
|
8
56
|
:extending, :unscope, :optimizer_hints, :annotate,
|
|
@@ -12,26 +60,34 @@ module ActiveRecord
|
|
|
12
60
|
:reverse_order, :distinct, :create_with, :skip_query_cache]
|
|
13
61
|
|
|
14
62
|
CLAUSE_METHODS = [:where, :having, :from]
|
|
15
|
-
|
|
63
|
+
INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL = [:distinct, :with, :with_recursive]
|
|
16
64
|
|
|
17
65
|
VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS
|
|
18
66
|
|
|
19
67
|
include Enumerable
|
|
20
68
|
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
|
|
69
|
+
include SignedId::RelationMethods, TokenFor::RelationMethods
|
|
21
70
|
|
|
22
|
-
attr_reader :table, :
|
|
71
|
+
attr_reader :table, :model, :loaded, :predicate_builder
|
|
23
72
|
attr_accessor :skip_preloading_value
|
|
24
|
-
alias :
|
|
73
|
+
alias :klass :model
|
|
25
74
|
alias :loaded? :loaded
|
|
26
75
|
alias :locked? :lock_value
|
|
27
76
|
|
|
28
|
-
def initialize(
|
|
29
|
-
|
|
77
|
+
def initialize(model, table: nil, predicate_builder: nil, values: {})
|
|
78
|
+
if table
|
|
79
|
+
predicate_builder ||= model.predicate_builder.with(TableMetadata.new(model, table))
|
|
80
|
+
else
|
|
81
|
+
table = model.arel_table
|
|
82
|
+
predicate_builder ||= model.predicate_builder
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
@model = model
|
|
30
86
|
@table = table
|
|
31
87
|
@values = values
|
|
32
88
|
@loaded = false
|
|
33
89
|
@predicate_builder = predicate_builder
|
|
34
|
-
@
|
|
90
|
+
@delegate_to_model = false
|
|
35
91
|
@future_result = nil
|
|
36
92
|
@records = nil
|
|
37
93
|
@async = false
|
|
@@ -44,7 +100,7 @@ module ActiveRecord
|
|
|
44
100
|
end
|
|
45
101
|
|
|
46
102
|
def bind_attribute(name, value) # :nodoc:
|
|
47
|
-
if reflection =
|
|
103
|
+
if reflection = model._reflect_on_association(name)
|
|
48
104
|
name = reflection.foreign_key
|
|
49
105
|
value = value.read_attribute(reflection.association_primary_key) unless value.nil?
|
|
50
106
|
end
|
|
@@ -207,18 +263,27 @@ module ActiveRecord
|
|
|
207
263
|
# the problem of running out of integers, if the underlying table is still stuck on a primary
|
|
208
264
|
# key of type int (note: All \Rails apps since 5.1+ have defaulted to bigint, which is not liable
|
|
209
265
|
# to this problem).
|
|
266
|
+
# * Columns with unique database constraints should not have uniqueness validations defined,
|
|
267
|
+
# otherwise #create will fail due to validation errors and #find_by will never be called.
|
|
210
268
|
#
|
|
211
269
|
# This method will return a record if all given attributes are covered by unique constraints
|
|
212
270
|
# (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
|
|
213
271
|
# and failed due to validation errors it won't be persisted, you get what #create returns in
|
|
214
272
|
# such situation.
|
|
215
273
|
def create_or_find_by(attributes, &block)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
274
|
+
with_connection do |connection|
|
|
275
|
+
record = nil
|
|
276
|
+
transaction(requires_new: true) do
|
|
277
|
+
record = create(attributes, &block)
|
|
278
|
+
record._last_transaction_return_status || raise(ActiveRecord::Rollback)
|
|
279
|
+
end
|
|
280
|
+
record
|
|
281
|
+
rescue ActiveRecord::RecordNotUnique
|
|
282
|
+
if connection.transaction_open?
|
|
283
|
+
where(attributes).lock.find_by!(attributes)
|
|
284
|
+
else
|
|
285
|
+
find_by!(attributes)
|
|
286
|
+
end
|
|
222
287
|
end
|
|
223
288
|
end
|
|
224
289
|
|
|
@@ -226,16 +291,23 @@ module ActiveRecord
|
|
|
226
291
|
# {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception
|
|
227
292
|
# is raised if the created record is invalid.
|
|
228
293
|
def create_or_find_by!(attributes, &block)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
294
|
+
with_connection do |connection|
|
|
295
|
+
record = nil
|
|
296
|
+
transaction(requires_new: true) do
|
|
297
|
+
record = create!(attributes, &block)
|
|
298
|
+
record._last_transaction_return_status || raise(ActiveRecord::Rollback)
|
|
299
|
+
end
|
|
300
|
+
record
|
|
301
|
+
rescue ActiveRecord::RecordNotUnique
|
|
302
|
+
if connection.transaction_open?
|
|
303
|
+
where(attributes).lock.find_by!(attributes)
|
|
304
|
+
else
|
|
305
|
+
find_by!(attributes)
|
|
306
|
+
end
|
|
235
307
|
end
|
|
236
308
|
end
|
|
237
309
|
|
|
238
|
-
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core
|
|
310
|
+
# Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
|
|
239
311
|
# instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
|
|
240
312
|
def find_or_initialize_by(attributes, &block)
|
|
241
313
|
find_by(attributes) || new(attributes, &block)
|
|
@@ -245,13 +317,30 @@ module ActiveRecord
|
|
|
245
317
|
# returns the result as a string. The string is formatted imitating the
|
|
246
318
|
# ones printed by the database shell.
|
|
247
319
|
#
|
|
320
|
+
# User.all.explain
|
|
321
|
+
# # EXPLAIN SELECT `users`.* FROM `users`
|
|
322
|
+
# # ...
|
|
323
|
+
#
|
|
248
324
|
# Note that this method actually runs the queries, since the results of some
|
|
249
325
|
# are needed by the next ones when eager loading is going on.
|
|
250
326
|
#
|
|
327
|
+
# To run EXPLAIN on queries created by +first+, +pluck+ and +count+, call
|
|
328
|
+
# these methods on +explain+:
|
|
329
|
+
#
|
|
330
|
+
# User.all.explain.count
|
|
331
|
+
# # EXPLAIN SELECT COUNT(*) FROM `users`
|
|
332
|
+
# # ...
|
|
333
|
+
#
|
|
334
|
+
# The column name can be passed if required:
|
|
335
|
+
#
|
|
336
|
+
# User.all.explain.maximum(:id)
|
|
337
|
+
# # EXPLAIN SELECT MAX(`users`.`id`) FROM `users`
|
|
338
|
+
# # ...
|
|
339
|
+
#
|
|
251
340
|
# Please see further details in the
|
|
252
341
|
# {Active Record Query Interface guide}[https://guides.rubyonrails.org/active_record_querying.html#running-explain].
|
|
253
342
|
def explain(*options)
|
|
254
|
-
|
|
343
|
+
ExplainProxy.new(self, options)
|
|
255
344
|
end
|
|
256
345
|
|
|
257
346
|
# Converts relation objects to Array.
|
|
@@ -358,14 +447,14 @@ module ActiveRecord
|
|
|
358
447
|
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
|
|
359
448
|
def cache_key(timestamp_column = "updated_at")
|
|
360
449
|
@cache_keys ||= {}
|
|
361
|
-
@cache_keys[timestamp_column] ||=
|
|
450
|
+
@cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column)
|
|
362
451
|
end
|
|
363
452
|
|
|
364
453
|
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
|
|
365
454
|
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
|
|
366
|
-
key = "#{
|
|
455
|
+
key = "#{model.model_name.cache_key}/query-#{query_signature}"
|
|
367
456
|
|
|
368
|
-
if collection_cache_versioning
|
|
457
|
+
if model.collection_cache_versioning
|
|
369
458
|
key
|
|
370
459
|
else
|
|
371
460
|
"#{key}-#{compute_cache_version(timestamp_column)}"
|
|
@@ -384,7 +473,7 @@ module ActiveRecord
|
|
|
384
473
|
#
|
|
385
474
|
# SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
|
|
386
475
|
def cache_version(timestamp_column = :updated_at)
|
|
387
|
-
if collection_cache_versioning
|
|
476
|
+
if model.collection_cache_versioning
|
|
388
477
|
@cache_versions ||= {}
|
|
389
478
|
@cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
|
|
390
479
|
end
|
|
@@ -401,33 +490,35 @@ module ActiveRecord
|
|
|
401
490
|
else
|
|
402
491
|
collection = eager_loading? ? apply_join_dependency : self
|
|
403
492
|
|
|
404
|
-
|
|
405
|
-
|
|
493
|
+
with_connection do |c|
|
|
494
|
+
column = c.visitor.compile(table[timestamp_column])
|
|
495
|
+
select_values = "COUNT(*) AS #{model.adapter_class.quote_column_name("size")}, MAX(%s) AS timestamp"
|
|
406
496
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
497
|
+
if collection.has_limit_or_offset?
|
|
498
|
+
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
|
499
|
+
query._select!(table[Arel.star]) if distinct_value && collection.select_values.empty?
|
|
500
|
+
subquery_alias = "subquery_for_cache_key"
|
|
501
|
+
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
|
|
502
|
+
arel = query.build_subquery(subquery_alias, select_values % subquery_column)
|
|
503
|
+
else
|
|
504
|
+
query = collection.unscope(:order)
|
|
505
|
+
query.select_values = [select_values % column]
|
|
506
|
+
arel = query.arel
|
|
507
|
+
end
|
|
418
508
|
|
|
419
|
-
|
|
509
|
+
size, timestamp = c.select_rows(arel, nil).first
|
|
420
510
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
511
|
+
if size
|
|
512
|
+
column_type = model.type_for_attribute(timestamp_column)
|
|
513
|
+
timestamp = column_type.deserialize(timestamp)
|
|
514
|
+
else
|
|
515
|
+
size = 0
|
|
516
|
+
end
|
|
426
517
|
end
|
|
427
518
|
end
|
|
428
519
|
|
|
429
520
|
if timestamp
|
|
430
|
-
"#{size}-#{timestamp.utc.to_fs(cache_timestamp_format)}"
|
|
521
|
+
"#{size}-#{timestamp.utc.to_fs(model.cache_timestamp_format)}"
|
|
431
522
|
else
|
|
432
523
|
"#{size}"
|
|
433
524
|
end
|
|
@@ -458,7 +549,7 @@ module ActiveRecord
|
|
|
458
549
|
# Please check unscoped if you want to remove all previous scopes (including
|
|
459
550
|
# the default_scope) during the execution of a block.
|
|
460
551
|
def scoping(all_queries: nil, &block)
|
|
461
|
-
registry =
|
|
552
|
+
registry = model.scope_registry
|
|
462
553
|
if global_scope?(registry) && all_queries == false
|
|
463
554
|
raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
|
|
464
555
|
elsif already_in_scope?(registry)
|
|
@@ -469,11 +560,11 @@ module ActiveRecord
|
|
|
469
560
|
end
|
|
470
561
|
|
|
471
562
|
def _exec_scope(...) # :nodoc:
|
|
472
|
-
@
|
|
473
|
-
registry =
|
|
563
|
+
@delegate_to_model = true
|
|
564
|
+
registry = model.scope_registry
|
|
474
565
|
_scoping(nil, registry) { instance_exec(...) || self }
|
|
475
566
|
ensure
|
|
476
|
-
@
|
|
567
|
+
@delegate_to_model = false
|
|
477
568
|
end
|
|
478
569
|
|
|
479
570
|
# Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
|
|
@@ -509,37 +600,49 @@ module ActiveRecord
|
|
|
509
600
|
|
|
510
601
|
return 0 if @none
|
|
511
602
|
|
|
603
|
+
invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
|
|
604
|
+
value = @values[method]
|
|
605
|
+
method == :distinct ? value : value&.any?
|
|
606
|
+
end
|
|
607
|
+
if invalid_methods.any?
|
|
608
|
+
ActiveRecord.deprecator.warn <<~MESSAGE
|
|
609
|
+
`#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query.
|
|
610
|
+
|
|
611
|
+
Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2.
|
|
612
|
+
MESSAGE
|
|
613
|
+
end
|
|
614
|
+
|
|
512
615
|
if updates.is_a?(Hash)
|
|
513
|
-
if
|
|
514
|
-
!updates.key?(
|
|
515
|
-
!updates.key?(
|
|
516
|
-
attr = table[
|
|
616
|
+
if model.locking_enabled? &&
|
|
617
|
+
!updates.key?(model.locking_column) &&
|
|
618
|
+
!updates.key?(model.locking_column.to_sym)
|
|
619
|
+
attr = table[model.locking_column]
|
|
517
620
|
updates[attr.name] = _increment_attribute(attr)
|
|
518
621
|
end
|
|
519
622
|
values = _substitute_values(updates)
|
|
520
623
|
else
|
|
521
|
-
values = Arel.sql(
|
|
624
|
+
values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name))
|
|
522
625
|
end
|
|
523
626
|
|
|
524
|
-
|
|
525
|
-
|
|
627
|
+
model.with_connection do |c|
|
|
628
|
+
arel = eager_loading? ? apply_join_dependency.arel : arel()
|
|
629
|
+
arel.source.left = table
|
|
526
630
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
631
|
+
key = if model.composite_primary_key?
|
|
632
|
+
primary_key.map { |pk| table[pk] }
|
|
633
|
+
else
|
|
634
|
+
table[primary_key]
|
|
635
|
+
end
|
|
636
|
+
stmt = arel.compile_update(values, key)
|
|
637
|
+
c.update(stmt, "#{model} Update All").tap { reset }
|
|
533
638
|
end
|
|
534
|
-
stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns)
|
|
535
|
-
klass.connection.update(stmt, "#{klass} Update All").tap { reset }
|
|
536
639
|
end
|
|
537
640
|
|
|
538
641
|
def update(id = :all, attributes) # :nodoc:
|
|
539
642
|
if id == :all
|
|
540
643
|
each { |record| record.update(attributes) }
|
|
541
644
|
else
|
|
542
|
-
|
|
645
|
+
model.update(id, attributes)
|
|
543
646
|
end
|
|
544
647
|
end
|
|
545
648
|
|
|
@@ -547,10 +650,289 @@ module ActiveRecord
|
|
|
547
650
|
if id == :all
|
|
548
651
|
each { |record| record.update!(attributes) }
|
|
549
652
|
else
|
|
550
|
-
|
|
653
|
+
model.update!(id, attributes)
|
|
551
654
|
end
|
|
552
655
|
end
|
|
553
656
|
|
|
657
|
+
|
|
658
|
+
# Inserts a single record into the database in a single SQL INSERT
|
|
659
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
660
|
+
# Active Record callbacks or validations. Though passed values
|
|
661
|
+
# go through Active Record's type casting and serialization.
|
|
662
|
+
#
|
|
663
|
+
# See #insert_all for documentation.
|
|
664
|
+
def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
665
|
+
insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
# Inserts multiple records into the database in a single SQL INSERT
|
|
669
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
670
|
+
# Active Record callbacks or validations. Though passed values
|
|
671
|
+
# go through Active Record's type casting and serialization.
|
|
672
|
+
#
|
|
673
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
|
674
|
+
# the attributes for a single row and must have the same keys.
|
|
675
|
+
#
|
|
676
|
+
# Rows are considered to be unique by every unique index on the table. Any
|
|
677
|
+
# duplicate rows are skipped.
|
|
678
|
+
# Override with <tt>:unique_by</tt> (see below).
|
|
679
|
+
#
|
|
680
|
+
# Returns an ActiveRecord::Result with its contents based on
|
|
681
|
+
# <tt>:returning</tt> (see below).
|
|
682
|
+
#
|
|
683
|
+
# ==== Options
|
|
684
|
+
#
|
|
685
|
+
# [:returning]
|
|
686
|
+
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
687
|
+
# inserted records, which by default is the primary key.
|
|
688
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
689
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
690
|
+
# clause entirely.
|
|
691
|
+
#
|
|
692
|
+
# You can also pass an SQL string if you need more control on the return values
|
|
693
|
+
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
|
694
|
+
#
|
|
695
|
+
# [:unique_by]
|
|
696
|
+
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
|
697
|
+
# by every unique index on the table. Any duplicate rows are skipped.
|
|
698
|
+
#
|
|
699
|
+
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
|
700
|
+
#
|
|
701
|
+
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
|
702
|
+
# row has an existing id, or is not unique by another unique index,
|
|
703
|
+
# ActiveRecord::RecordNotUnique is raised.
|
|
704
|
+
#
|
|
705
|
+
# Unique indexes can be identified by columns or name:
|
|
706
|
+
#
|
|
707
|
+
# unique_by: :isbn
|
|
708
|
+
# unique_by: %i[ author_id name ]
|
|
709
|
+
# unique_by: :index_books_on_isbn
|
|
710
|
+
#
|
|
711
|
+
# [:record_timestamps]
|
|
712
|
+
# By default, automatic setting of timestamp columns is controlled by
|
|
713
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
|
714
|
+
# behavior.
|
|
715
|
+
#
|
|
716
|
+
# To override this and force automatic setting of timestamp columns one
|
|
717
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
|
718
|
+
#
|
|
719
|
+
# record_timestamps: true # Always set timestamps automatically
|
|
720
|
+
# record_timestamps: false # Never set timestamps automatically
|
|
721
|
+
#
|
|
722
|
+
# Because it relies on the index information from the database
|
|
723
|
+
# <tt>:unique_by</tt> is recommended to be paired with
|
|
724
|
+
# Active Record's schema_cache.
|
|
725
|
+
#
|
|
726
|
+
# ==== Example
|
|
727
|
+
#
|
|
728
|
+
# # Insert records and skip inserting any duplicates.
|
|
729
|
+
# # Here "Eloquent Ruby" is skipped because its id is not unique.
|
|
730
|
+
#
|
|
731
|
+
# Book.insert_all([
|
|
732
|
+
# { id: 1, title: "Rework", author: "David" },
|
|
733
|
+
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
|
734
|
+
# ])
|
|
735
|
+
#
|
|
736
|
+
# # insert_all works on chained scopes, and you can use create_with
|
|
737
|
+
# # to set default attributes for all inserted records.
|
|
738
|
+
#
|
|
739
|
+
# author.books.create_with(created_at: Time.now).insert_all([
|
|
740
|
+
# { id: 1, title: "Rework" },
|
|
741
|
+
# { id: 2, title: "Eloquent Ruby" }
|
|
742
|
+
# ])
|
|
743
|
+
def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
744
|
+
InsertAll.execute(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
# Inserts a single record into the database in a single SQL INSERT
|
|
748
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
749
|
+
# Active Record callbacks or validations. Though passed values
|
|
750
|
+
# go through Active Record's type casting and serialization.
|
|
751
|
+
#
|
|
752
|
+
# See #insert_all! for more.
|
|
753
|
+
def insert!(attributes, returning: nil, record_timestamps: nil)
|
|
754
|
+
insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps)
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
# Inserts multiple records into the database in a single SQL INSERT
|
|
758
|
+
# statement. It does not instantiate any models nor does it trigger
|
|
759
|
+
# Active Record callbacks or validations. Though passed values
|
|
760
|
+
# go through Active Record's type casting and serialization.
|
|
761
|
+
#
|
|
762
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
|
763
|
+
# the attributes for a single row and must have the same keys.
|
|
764
|
+
#
|
|
765
|
+
# Raises ActiveRecord::RecordNotUnique if any rows violate a
|
|
766
|
+
# unique index on the table. In that case, no rows are inserted.
|
|
767
|
+
#
|
|
768
|
+
# To skip duplicate rows, see #insert_all. To replace them, see #upsert_all.
|
|
769
|
+
#
|
|
770
|
+
# Returns an ActiveRecord::Result with its contents based on
|
|
771
|
+
# <tt>:returning</tt> (see below).
|
|
772
|
+
#
|
|
773
|
+
# ==== Options
|
|
774
|
+
#
|
|
775
|
+
# [:returning]
|
|
776
|
+
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
777
|
+
# inserted records, which by default is the primary key.
|
|
778
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
779
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
780
|
+
# clause entirely.
|
|
781
|
+
#
|
|
782
|
+
# You can also pass an SQL string if you need more control on the return values
|
|
783
|
+
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
|
784
|
+
#
|
|
785
|
+
# [:record_timestamps]
|
|
786
|
+
# By default, automatic setting of timestamp columns is controlled by
|
|
787
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
|
788
|
+
# behavior.
|
|
789
|
+
#
|
|
790
|
+
# To override this and force automatic setting of timestamp columns one
|
|
791
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
|
792
|
+
#
|
|
793
|
+
# record_timestamps: true # Always set timestamps automatically
|
|
794
|
+
# record_timestamps: false # Never set timestamps automatically
|
|
795
|
+
#
|
|
796
|
+
# ==== Examples
|
|
797
|
+
#
|
|
798
|
+
# # Insert multiple records
|
|
799
|
+
# Book.insert_all!([
|
|
800
|
+
# { title: "Rework", author: "David" },
|
|
801
|
+
# { title: "Eloquent Ruby", author: "Russ" }
|
|
802
|
+
# ])
|
|
803
|
+
#
|
|
804
|
+
# # Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
|
|
805
|
+
# # does not have a unique id.
|
|
806
|
+
# Book.insert_all!([
|
|
807
|
+
# { id: 1, title: "Rework", author: "David" },
|
|
808
|
+
# { id: 1, title: "Eloquent Ruby", author: "Russ" }
|
|
809
|
+
# ])
|
|
810
|
+
def insert_all!(attributes, returning: nil, record_timestamps: nil)
|
|
811
|
+
InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps)
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
# Updates or inserts (upserts) a single record into the database in a
|
|
815
|
+
# single SQL INSERT statement. It does not instantiate any models nor does
|
|
816
|
+
# it trigger Active Record callbacks or validations. Though passed values
|
|
817
|
+
# go through Active Record's type casting and serialization.
|
|
818
|
+
#
|
|
819
|
+
# See #upsert_all for documentation.
|
|
820
|
+
def upsert(attributes, **kwargs)
|
|
821
|
+
upsert_all([ attributes ], **kwargs)
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
# Updates or inserts (upserts) multiple records into the database in a
|
|
825
|
+
# single SQL INSERT statement. It does not instantiate any models nor does
|
|
826
|
+
# it trigger Active Record callbacks or validations. Though passed values
|
|
827
|
+
# go through Active Record's type casting and serialization.
|
|
828
|
+
#
|
|
829
|
+
# The +attributes+ parameter is an Array of Hashes. Every Hash determines
|
|
830
|
+
# the attributes for a single row and must have the same keys.
|
|
831
|
+
#
|
|
832
|
+
# Returns an ActiveRecord::Result with its contents based on
|
|
833
|
+
# <tt>:returning</tt> (see below).
|
|
834
|
+
#
|
|
835
|
+
# By default, +upsert_all+ will update all the columns that can be updated when
|
|
836
|
+
# there is a conflict. These are all the columns except primary keys, read-only
|
|
837
|
+
# columns, and columns covered by the optional +unique_by+.
|
|
838
|
+
#
|
|
839
|
+
# ==== Options
|
|
840
|
+
#
|
|
841
|
+
# [:returning]
|
|
842
|
+
# (PostgreSQL, SQLite3, and MariaDB only) An array of attributes to return for all successfully
|
|
843
|
+
# upserted records, which by default is the primary key.
|
|
844
|
+
# Pass <tt>returning: %w[ id name ]</tt> for both id and name
|
|
845
|
+
# or <tt>returning: false</tt> to omit the underlying <tt>RETURNING</tt> SQL
|
|
846
|
+
# clause entirely.
|
|
847
|
+
#
|
|
848
|
+
# You can also pass an SQL string if you need more control on the return values
|
|
849
|
+
# (for example, <tt>returning: Arel.sql("id, name as new_name")</tt>).
|
|
850
|
+
#
|
|
851
|
+
# [:unique_by]
|
|
852
|
+
# (PostgreSQL and SQLite only) By default rows are considered to be unique
|
|
853
|
+
# by every unique index on the table. Any duplicate rows are skipped.
|
|
854
|
+
#
|
|
855
|
+
# To skip rows according to just one unique index pass <tt>:unique_by</tt>.
|
|
856
|
+
#
|
|
857
|
+
# Consider a Book model where no duplicate ISBNs make sense, but if any
|
|
858
|
+
# row has an existing id, or is not unique by another unique index,
|
|
859
|
+
# ActiveRecord::RecordNotUnique is raised.
|
|
860
|
+
#
|
|
861
|
+
# Unique indexes can be identified by columns or name:
|
|
862
|
+
#
|
|
863
|
+
# unique_by: :isbn
|
|
864
|
+
# unique_by: %i[ author_id name ]
|
|
865
|
+
# unique_by: :index_books_on_isbn
|
|
866
|
+
#
|
|
867
|
+
# Because it relies on the index information from the database
|
|
868
|
+
# <tt>:unique_by</tt> is recommended to be paired with
|
|
869
|
+
# Active Record's schema_cache.
|
|
870
|
+
#
|
|
871
|
+
# [:on_duplicate]
|
|
872
|
+
# Configure the behavior that will be used in case of conflict. Use `:skip`
|
|
873
|
+
# to ignore any conflicts or provide a safe SQL fragment wrapped with
|
|
874
|
+
# `Arel.sql`.
|
|
875
|
+
#
|
|
876
|
+
# NOTE: If you use this option you must provide all the columns you want to update
|
|
877
|
+
# by yourself.
|
|
878
|
+
#
|
|
879
|
+
# Example:
|
|
880
|
+
#
|
|
881
|
+
# Commodity.upsert_all(
|
|
882
|
+
# [
|
|
883
|
+
# { id: 2, name: "Copper", price: 4.84 },
|
|
884
|
+
# { id: 4, name: "Gold", price: 1380.87 },
|
|
885
|
+
# { id: 6, name: "Aluminium", price: 0.35 }
|
|
886
|
+
# ],
|
|
887
|
+
# on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)")
|
|
888
|
+
# )
|
|
889
|
+
#
|
|
890
|
+
# See the related +:update_only+ option. Both options can't be used at the same time.
|
|
891
|
+
#
|
|
892
|
+
# [:update_only]
|
|
893
|
+
# Provide a list of column names that will be updated in case of conflict. If not provided,
|
|
894
|
+
# +upsert_all+ will update all the columns that can be updated. These are all the columns
|
|
895
|
+
# except primary keys, read-only columns, and columns covered by the optional +unique_by+
|
|
896
|
+
#
|
|
897
|
+
# Example:
|
|
898
|
+
#
|
|
899
|
+
# Commodity.upsert_all(
|
|
900
|
+
# [
|
|
901
|
+
# { id: 2, name: "Copper", price: 4.84 },
|
|
902
|
+
# { id: 4, name: "Gold", price: 1380.87 },
|
|
903
|
+
# { id: 6, name: "Aluminium", price: 0.35 }
|
|
904
|
+
# ],
|
|
905
|
+
# update_only: [:price] # Only prices will be updated
|
|
906
|
+
# )
|
|
907
|
+
#
|
|
908
|
+
# See the related +:on_duplicate+ option. Both options can't be used at the same time.
|
|
909
|
+
#
|
|
910
|
+
# [:record_timestamps]
|
|
911
|
+
# By default, automatic setting of timestamp columns is controlled by
|
|
912
|
+
# the model's <tt>record_timestamps</tt> config, matching typical
|
|
913
|
+
# behavior.
|
|
914
|
+
#
|
|
915
|
+
# To override this and force automatic setting of timestamp columns one
|
|
916
|
+
# way or the other, pass <tt>:record_timestamps</tt>:
|
|
917
|
+
#
|
|
918
|
+
# record_timestamps: true # Always set timestamps automatically
|
|
919
|
+
# record_timestamps: false # Never set timestamps automatically
|
|
920
|
+
#
|
|
921
|
+
# ==== Examples
|
|
922
|
+
#
|
|
923
|
+
# # Inserts multiple records, performing an upsert when records have duplicate ISBNs.
|
|
924
|
+
# # Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
|
|
925
|
+
#
|
|
926
|
+
# Book.upsert_all([
|
|
927
|
+
# { title: "Rework", author: "David", isbn: "1" },
|
|
928
|
+
# { title: "Eloquent Ruby", author: "Russ", isbn: "1" }
|
|
929
|
+
# ], unique_by: :isbn)
|
|
930
|
+
#
|
|
931
|
+
# Book.find_by(isbn: "1").title # => "Eloquent Ruby"
|
|
932
|
+
def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
|
|
933
|
+
InsertAll.execute(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps)
|
|
934
|
+
end
|
|
935
|
+
|
|
554
936
|
# Updates the counters of the records in the current relation.
|
|
555
937
|
#
|
|
556
938
|
# ==== Parameters
|
|
@@ -576,7 +958,7 @@ module ActiveRecord
|
|
|
576
958
|
names = touch if touch != true
|
|
577
959
|
names = Array.wrap(names)
|
|
578
960
|
options = names.extract_options!
|
|
579
|
-
touch_updates =
|
|
961
|
+
touch_updates = model.touch_attributes_with_time(*names, **options)
|
|
580
962
|
updates.merge!(touch_updates) unless touch_updates.empty?
|
|
581
963
|
end
|
|
582
964
|
|
|
@@ -589,7 +971,7 @@ module ActiveRecord
|
|
|
589
971
|
# If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
|
590
972
|
# If no time argument is passed, the current time is used as default.
|
|
591
973
|
#
|
|
592
|
-
#
|
|
974
|
+
# ==== Examples
|
|
593
975
|
#
|
|
594
976
|
# # Touch all records
|
|
595
977
|
# Person.all.touch_all
|
|
@@ -607,7 +989,7 @@ module ActiveRecord
|
|
|
607
989
|
# Person.where(name: 'David').touch_all
|
|
608
990
|
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
|
|
609
991
|
def touch_all(*names, time: nil)
|
|
610
|
-
update_all
|
|
992
|
+
update_all model.touch_attributes_with_time(*names, time: time)
|
|
611
993
|
end
|
|
612
994
|
|
|
613
995
|
# Destroys the records by instantiating each
|
|
@@ -640,7 +1022,7 @@ module ActiveRecord
|
|
|
640
1022
|
#
|
|
641
1023
|
# Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
|
|
642
1024
|
#
|
|
643
|
-
#
|
|
1025
|
+
# This call deletes the affected posts all at once with a single DELETE statement.
|
|
644
1026
|
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
|
|
645
1027
|
# +after_destroy+ callbacks, use the #destroy_all method instead.
|
|
646
1028
|
#
|
|
@@ -651,7 +1033,7 @@ module ActiveRecord
|
|
|
651
1033
|
def delete_all
|
|
652
1034
|
return 0 if @none
|
|
653
1035
|
|
|
654
|
-
invalid_methods =
|
|
1036
|
+
invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method|
|
|
655
1037
|
value = @values[method]
|
|
656
1038
|
method == :distinct ? value : value&.any?
|
|
657
1039
|
end
|
|
@@ -659,19 +1041,77 @@ module ActiveRecord
|
|
|
659
1041
|
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
|
660
1042
|
end
|
|
661
1043
|
|
|
662
|
-
|
|
663
|
-
|
|
1044
|
+
model.with_connection do |c|
|
|
1045
|
+
arel = eager_loading? ? apply_join_dependency.arel : arel()
|
|
1046
|
+
arel.source.left = table
|
|
664
1047
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1048
|
+
key = if model.composite_primary_key?
|
|
1049
|
+
primary_key.map { |pk| table[pk] }
|
|
1050
|
+
else
|
|
1051
|
+
table[primary_key]
|
|
1052
|
+
end
|
|
1053
|
+
stmt = arel.compile_delete(key)
|
|
1054
|
+
|
|
1055
|
+
c.delete(stmt, "#{model} Delete All").tap { reset }
|
|
1056
|
+
end
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
# Deletes the row with a primary key matching the +id+ argument, using an
|
|
1060
|
+
# SQL +DELETE+ statement, and returns the number of rows deleted. Active
|
|
1061
|
+
# Record objects are not instantiated, so the object's callbacks are not
|
|
1062
|
+
# executed, including any <tt>:dependent</tt> association options.
|
|
1063
|
+
#
|
|
1064
|
+
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
|
1065
|
+
#
|
|
1066
|
+
# Note: Although it is often much faster than the alternative, #destroy,
|
|
1067
|
+
# skipping callbacks might bypass business logic in your application
|
|
1068
|
+
# that ensures referential integrity or performs other essential jobs.
|
|
1069
|
+
#
|
|
1070
|
+
# ==== Examples
|
|
1071
|
+
#
|
|
1072
|
+
# # Delete a single row
|
|
1073
|
+
# Todo.delete(1)
|
|
1074
|
+
#
|
|
1075
|
+
# # Delete multiple rows
|
|
1076
|
+
# Todo.delete([2,3,4])
|
|
1077
|
+
def delete(id_or_array)
|
|
1078
|
+
return 0 if id_or_array.nil? || (id_or_array.is_a?(Array) && id_or_array.empty?)
|
|
1079
|
+
|
|
1080
|
+
where(model.primary_key => id_or_array).delete_all
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
# Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
|
|
1085
|
+
# therefore all callbacks and filters are fired off before the object is deleted. This method is
|
|
1086
|
+
# less efficient than #delete but allows cleanup methods and other actions to be run.
|
|
1087
|
+
#
|
|
1088
|
+
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
|
1089
|
+
# from the attributes, and then calls destroy on it.
|
|
1090
|
+
#
|
|
1091
|
+
# ==== Parameters
|
|
1092
|
+
#
|
|
1093
|
+
# * +id+ - This should be the id or an array of ids to be destroyed.
|
|
1094
|
+
#
|
|
1095
|
+
# ==== Examples
|
|
1096
|
+
#
|
|
1097
|
+
# # Destroy a single object
|
|
1098
|
+
# Todo.destroy(1)
|
|
1099
|
+
#
|
|
1100
|
+
# # Destroy multiple objects
|
|
1101
|
+
# todos = [1,2,3]
|
|
1102
|
+
# Todo.destroy(todos)
|
|
1103
|
+
def destroy(id)
|
|
1104
|
+
multiple_ids = if model.composite_primary_key?
|
|
1105
|
+
id.first.is_a?(Array)
|
|
669
1106
|
else
|
|
670
|
-
|
|
1107
|
+
id.is_a?(Array)
|
|
671
1108
|
end
|
|
672
|
-
stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
|
|
673
1109
|
|
|
674
|
-
|
|
1110
|
+
if multiple_ids
|
|
1111
|
+
find(id).each(&:destroy)
|
|
1112
|
+
else
|
|
1113
|
+
find(id).destroy
|
|
1114
|
+
end
|
|
675
1115
|
end
|
|
676
1116
|
|
|
677
1117
|
# Finds and destroys all records matching the specified conditions.
|
|
@@ -711,30 +1151,39 @@ module ActiveRecord
|
|
|
711
1151
|
# for queries to actually be executed concurrently. Otherwise it defaults to
|
|
712
1152
|
# executing them in the foreground.
|
|
713
1153
|
#
|
|
714
|
-
# +load_async+ will also fall back to executing in the foreground in the test environment when transactional
|
|
715
|
-
# fixtures are enabled.
|
|
716
|
-
#
|
|
717
1154
|
# If the query was actually executed in the background, the Active Record logs will show
|
|
718
1155
|
# it by prefixing the log line with <tt>ASYNC</tt>:
|
|
719
1156
|
#
|
|
720
1157
|
# ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
|
|
721
1158
|
def load_async
|
|
722
|
-
|
|
1159
|
+
with_connection do |c|
|
|
1160
|
+
return load if !c.async_enabled?
|
|
723
1161
|
|
|
724
|
-
|
|
725
|
-
|
|
1162
|
+
unless loaded?
|
|
1163
|
+
result = exec_main_query(async: !c.current_transaction.joinable?)
|
|
726
1164
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1165
|
+
if result.is_a?(Array)
|
|
1166
|
+
@records = result
|
|
1167
|
+
else
|
|
1168
|
+
@future_result = result
|
|
1169
|
+
end
|
|
1170
|
+
@loaded = true
|
|
731
1171
|
end
|
|
732
|
-
@loaded = true
|
|
733
1172
|
end
|
|
734
1173
|
|
|
735
1174
|
self
|
|
736
1175
|
end
|
|
737
1176
|
|
|
1177
|
+
def then(&block) # :nodoc:
|
|
1178
|
+
if @future_result
|
|
1179
|
+
@future_result.then do
|
|
1180
|
+
yield self
|
|
1181
|
+
end
|
|
1182
|
+
else
|
|
1183
|
+
super
|
|
1184
|
+
end
|
|
1185
|
+
end
|
|
1186
|
+
|
|
738
1187
|
# Returns <tt>true</tt> if the relation was scheduled on the background
|
|
739
1188
|
# thread pool.
|
|
740
1189
|
def scheduled?
|
|
@@ -765,7 +1214,7 @@ module ActiveRecord
|
|
|
765
1214
|
def reset
|
|
766
1215
|
@future_result&.cancel
|
|
767
1216
|
@future_result = nil
|
|
768
|
-
@
|
|
1217
|
+
@delegate_to_model = false
|
|
769
1218
|
@to_sql = @arel = @loaded = @should_eager_load = nil
|
|
770
1219
|
@offsets = @take = nil
|
|
771
1220
|
@cache_keys = nil
|
|
@@ -785,8 +1234,9 @@ module ActiveRecord
|
|
|
785
1234
|
relation.to_sql
|
|
786
1235
|
end
|
|
787
1236
|
else
|
|
788
|
-
|
|
789
|
-
|
|
1237
|
+
model.with_connection do |conn|
|
|
1238
|
+
conn.unprepared_statement { conn.to_sql(arel) }
|
|
1239
|
+
end
|
|
790
1240
|
end
|
|
791
1241
|
end
|
|
792
1242
|
|
|
@@ -794,12 +1244,12 @@ module ActiveRecord
|
|
|
794
1244
|
#
|
|
795
1245
|
# User.where(name: 'Oscar').where_values_hash
|
|
796
1246
|
# # => {name: "Oscar"}
|
|
797
|
-
def where_values_hash(relation_table_name =
|
|
1247
|
+
def where_values_hash(relation_table_name = model.table_name) # :nodoc:
|
|
798
1248
|
where_clause.to_h(relation_table_name)
|
|
799
1249
|
end
|
|
800
1250
|
|
|
801
1251
|
def scope_for_create
|
|
802
|
-
hash = where_clause.to_h(
|
|
1252
|
+
hash = where_clause.to_h(model.table_name, equality_only: true)
|
|
803
1253
|
create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty?
|
|
804
1254
|
hash
|
|
805
1255
|
end
|
|
@@ -845,6 +1295,10 @@ module ActiveRecord
|
|
|
845
1295
|
records.blank?
|
|
846
1296
|
end
|
|
847
1297
|
|
|
1298
|
+
def readonly?
|
|
1299
|
+
readonly_value
|
|
1300
|
+
end
|
|
1301
|
+
|
|
848
1302
|
def values
|
|
849
1303
|
@values.dup
|
|
850
1304
|
end
|
|
@@ -863,7 +1317,7 @@ module ActiveRecord
|
|
|
863
1317
|
end
|
|
864
1318
|
|
|
865
1319
|
def empty_scope? # :nodoc:
|
|
866
|
-
@values ==
|
|
1320
|
+
@values == model.unscoped.values
|
|
867
1321
|
end
|
|
868
1322
|
|
|
869
1323
|
def has_limit_or_offset? # :nodoc:
|
|
@@ -871,7 +1325,7 @@ module ActiveRecord
|
|
|
871
1325
|
end
|
|
872
1326
|
|
|
873
1327
|
def alias_tracker(joins = [], aliases = nil) # :nodoc:
|
|
874
|
-
ActiveRecord::Associations::AliasTracker.create(
|
|
1328
|
+
ActiveRecord::Associations::AliasTracker.create(model.connection_pool, table.name, joins, aliases)
|
|
875
1329
|
end
|
|
876
1330
|
|
|
877
1331
|
class StrictLoadingScope # :nodoc:
|
|
@@ -901,54 +1355,58 @@ module ActiveRecord
|
|
|
901
1355
|
|
|
902
1356
|
private
|
|
903
1357
|
def already_in_scope?(registry)
|
|
904
|
-
@
|
|
1358
|
+
@delegate_to_model && registry.current_scope(model, true)
|
|
905
1359
|
end
|
|
906
1360
|
|
|
907
1361
|
def global_scope?(registry)
|
|
908
|
-
registry.global_current_scope(
|
|
1362
|
+
registry.global_current_scope(model, true)
|
|
909
1363
|
end
|
|
910
1364
|
|
|
911
1365
|
def current_scope_restoring_block(&block)
|
|
912
|
-
current_scope =
|
|
1366
|
+
current_scope = model.current_scope(true)
|
|
913
1367
|
-> record do
|
|
914
|
-
|
|
1368
|
+
model.current_scope = current_scope
|
|
915
1369
|
yield record if block_given?
|
|
916
1370
|
end
|
|
917
1371
|
end
|
|
918
1372
|
|
|
919
1373
|
def _new(attributes, &block)
|
|
920
|
-
|
|
1374
|
+
model.new(attributes, &block)
|
|
921
1375
|
end
|
|
922
1376
|
|
|
923
1377
|
def _create(attributes, &block)
|
|
924
|
-
|
|
1378
|
+
model.create(attributes, &block)
|
|
925
1379
|
end
|
|
926
1380
|
|
|
927
1381
|
def _create!(attributes, &block)
|
|
928
|
-
|
|
1382
|
+
model.create!(attributes, &block)
|
|
929
1383
|
end
|
|
930
1384
|
|
|
931
1385
|
def _scoping(scope, registry, all_queries = false)
|
|
932
|
-
previous = registry.current_scope(
|
|
933
|
-
registry.set_current_scope(
|
|
1386
|
+
previous = registry.current_scope(model, true)
|
|
1387
|
+
registry.set_current_scope(model, scope)
|
|
934
1388
|
|
|
935
1389
|
if all_queries
|
|
936
|
-
previous_global = registry.global_current_scope(
|
|
937
|
-
registry.set_global_current_scope(
|
|
1390
|
+
previous_global = registry.global_current_scope(model, true)
|
|
1391
|
+
registry.set_global_current_scope(model, scope)
|
|
938
1392
|
end
|
|
939
1393
|
yield
|
|
940
1394
|
ensure
|
|
941
|
-
registry.set_current_scope(
|
|
1395
|
+
registry.set_current_scope(model, previous)
|
|
942
1396
|
if all_queries
|
|
943
|
-
registry.set_global_current_scope(
|
|
1397
|
+
registry.set_global_current_scope(model, previous_global)
|
|
944
1398
|
end
|
|
945
1399
|
end
|
|
946
1400
|
|
|
947
1401
|
def _substitute_values(values)
|
|
948
1402
|
values.map do |name, value|
|
|
949
1403
|
attr = table[name]
|
|
950
|
-
|
|
951
|
-
|
|
1404
|
+
if Arel.arel_node?(value)
|
|
1405
|
+
if value.is_a?(Arel::Nodes::SqlLiteral)
|
|
1406
|
+
value = Arel::Nodes::Grouping.new(value)
|
|
1407
|
+
end
|
|
1408
|
+
else
|
|
1409
|
+
type = model.type_for_attribute(attr.name)
|
|
952
1410
|
value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
|
|
953
1411
|
end
|
|
954
1412
|
[attr, value]
|
|
@@ -957,12 +1415,16 @@ module ActiveRecord
|
|
|
957
1415
|
|
|
958
1416
|
def _increment_attribute(attribute, value = 1)
|
|
959
1417
|
bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
|
|
960
|
-
expr = table.coalesce(
|
|
1418
|
+
expr = table.coalesce(attribute, 0)
|
|
961
1419
|
expr = value < 0 ? expr - bind : expr + bind
|
|
962
1420
|
expr.expr
|
|
963
1421
|
end
|
|
964
1422
|
|
|
965
1423
|
def exec_queries(&block)
|
|
1424
|
+
if lock_value && model.current_preventing_writes
|
|
1425
|
+
raise ActiveRecord::ReadOnlyError, "Lock query attempted while in readonly mode"
|
|
1426
|
+
end
|
|
1427
|
+
|
|
966
1428
|
skip_query_cache_if_necessary do
|
|
967
1429
|
rows = if scheduled?
|
|
968
1430
|
future = @future_result
|
|
@@ -995,17 +1457,21 @@ module ActiveRecord
|
|
|
995
1457
|
if where_clause.contradiction?
|
|
996
1458
|
[].freeze
|
|
997
1459
|
elsif eager_loading?
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1460
|
+
model.with_connection do |c|
|
|
1461
|
+
apply_join_dependency do |relation, join_dependency|
|
|
1462
|
+
if relation.null_relation?
|
|
1463
|
+
[].freeze
|
|
1464
|
+
else
|
|
1465
|
+
relation = join_dependency.apply_column_aliases(relation)
|
|
1466
|
+
@_join_dependency = join_dependency
|
|
1467
|
+
c.select_all(relation.arel, "#{model.name} Eager Load", async: async)
|
|
1468
|
+
end
|
|
1005
1469
|
end
|
|
1006
1470
|
end
|
|
1007
1471
|
else
|
|
1008
|
-
|
|
1472
|
+
model.with_connection do |c|
|
|
1473
|
+
model._query_by_sql(c, arel, async: async)
|
|
1474
|
+
end
|
|
1009
1475
|
end
|
|
1010
1476
|
end
|
|
1011
1477
|
end
|
|
@@ -1017,13 +1483,13 @@ module ActiveRecord
|
|
|
1017
1483
|
@_join_dependency = nil
|
|
1018
1484
|
records
|
|
1019
1485
|
else
|
|
1020
|
-
|
|
1486
|
+
model._load_from_sql(rows, &block).freeze
|
|
1021
1487
|
end
|
|
1022
1488
|
end
|
|
1023
1489
|
|
|
1024
1490
|
def skip_query_cache_if_necessary(&block)
|
|
1025
1491
|
if skip_query_cache_value
|
|
1026
|
-
uncached(&block)
|
|
1492
|
+
model.uncached(&block)
|
|
1027
1493
|
else
|
|
1028
1494
|
yield
|
|
1029
1495
|
end
|