activerecord 6.0.0.beta1 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +455 -9
- data/README.rdoc +3 -1
- data/lib/active_record/associations/association.rb +18 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/join_dependency.rb +10 -9
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +15 -5
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
- data/lib/active_record/connection_handling.rb +32 -16
- data/lib/active_record/core.rb +27 -20
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +13 -13
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +217 -68
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/relation.rb +184 -35
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +55 -45
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record.rb +7 -1
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/arel.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +17 -13
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
class Relation
|
6
6
|
MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
|
7
7
|
:order, :joins, :left_outer_joins, :references,
|
8
|
-
:extending, :unscope]
|
8
|
+
:extending, :unscope, :optimizer_hints, :annotate]
|
9
9
|
|
10
10
|
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering,
|
11
11
|
:reverse_order, :distinct, :create_with, :skip_query_cache]
|
@@ -67,6 +67,7 @@ module ActiveRecord
|
|
67
67
|
# user = users.new { |user| user.name = 'Oscar' }
|
68
68
|
# user.name # => Oscar
|
69
69
|
def new(attributes = nil, &block)
|
70
|
+
block = _deprecated_scope_block("new", &block)
|
70
71
|
scoping { klass.new(attributes, &block) }
|
71
72
|
end
|
72
73
|
|
@@ -92,7 +93,12 @@ module ActiveRecord
|
|
92
93
|
# users.create(name: nil) # validation on name
|
93
94
|
# # => #<User id: nil, name: nil, ...>
|
94
95
|
def create(attributes = nil, &block)
|
95
|
-
|
96
|
+
if attributes.is_a?(Array)
|
97
|
+
attributes.collect { |attr| create(attr, &block) }
|
98
|
+
else
|
99
|
+
block = _deprecated_scope_block("create", &block)
|
100
|
+
scoping { klass.create(attributes, &block) }
|
101
|
+
end
|
96
102
|
end
|
97
103
|
|
98
104
|
# Similar to #create, but calls
|
@@ -102,7 +108,12 @@ module ActiveRecord
|
|
102
108
|
# Expects arguments in the same format as
|
103
109
|
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
|
104
110
|
def create!(attributes = nil, &block)
|
105
|
-
|
111
|
+
if attributes.is_a?(Array)
|
112
|
+
attributes.collect { |attr| create!(attr, &block) }
|
113
|
+
else
|
114
|
+
block = _deprecated_scope_block("create!", &block)
|
115
|
+
scoping { klass.create!(attributes, &block) }
|
116
|
+
end
|
106
117
|
end
|
107
118
|
|
108
119
|
def first_or_create(attributes = nil, &block) # :nodoc:
|
@@ -186,6 +197,10 @@ module ActiveRecord
|
|
186
197
|
# if a DELETE between those two statements is run by another client. But for most applications,
|
187
198
|
# that's a significantly less likely condition to hit.
|
188
199
|
# * It relies on exception handling to handle control flow, which may be marginally slower.
|
200
|
+
# * The primary key may auto-increment on each create, even if it fails. This can accelerate
|
201
|
+
# the problem of running out of integers, if the underlying table is still stuck on a primary
|
202
|
+
# key of type int (note: All Rails apps since 5.1+ have defaulted to bigint, which is not liable
|
203
|
+
# to this problem).
|
189
204
|
#
|
190
205
|
# This method will return a record if all given attributes are covered by unique constraints
|
191
206
|
# (unless the INSERT -> DELETE -> SELECT race condition is triggered), but if creation was attempted
|
@@ -276,31 +291,99 @@ module ActiveRecord
|
|
276
291
|
limit_value ? records.many? : size > 1
|
277
292
|
end
|
278
293
|
|
279
|
-
# Returns a cache key that can be used to identify
|
280
|
-
#
|
281
|
-
# the number of records matched by the query and a timestamp of the last
|
282
|
-
# updated record. When a new record comes to match the query, or any of
|
283
|
-
# the existing records is updated or deleted, the cache key changes.
|
294
|
+
# Returns a stable cache key that can be used to identify this query.
|
295
|
+
# The cache key is built with a fingerprint of the SQL query.
|
284
296
|
#
|
285
|
-
#
|
286
|
-
#
|
297
|
+
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
298
|
+
# # => "products/query-1850ab3d302391b85b8693e941286659"
|
287
299
|
#
|
288
|
-
# If
|
289
|
-
#
|
300
|
+
# If ActiveRecord::Base.collection_cache_versioning is turned off, as it was
|
301
|
+
# in Rails 6.0 and earlier, the cache key will also include a version.
|
290
302
|
#
|
291
|
-
#
|
303
|
+
# ActiveRecord::Base.collection_cache_versioning = false
|
304
|
+
# Product.where("name like ?", "%Cosmic Encounter%").cache_key
|
305
|
+
# # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
|
292
306
|
#
|
293
307
|
# You can also pass a custom timestamp column to fetch the timestamp of the
|
294
308
|
# last updated record.
|
295
309
|
#
|
296
310
|
# Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
|
297
|
-
#
|
298
|
-
# You can customize the strategy to generate the key on a per model basis
|
299
|
-
# overriding ActiveRecord::Base#collection_cache_key.
|
300
311
|
def cache_key(timestamp_column = :updated_at)
|
301
312
|
@cache_keys ||= {}
|
302
|
-
@cache_keys[timestamp_column] ||=
|
313
|
+
@cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
|
314
|
+
end
|
315
|
+
|
316
|
+
def compute_cache_key(timestamp_column = :updated_at) # :nodoc:
|
317
|
+
query_signature = ActiveSupport::Digest.hexdigest(to_sql)
|
318
|
+
key = "#{klass.model_name.cache_key}/query-#{query_signature}"
|
319
|
+
|
320
|
+
if cache_version(timestamp_column)
|
321
|
+
key
|
322
|
+
else
|
323
|
+
"#{key}-#{compute_cache_version(timestamp_column)}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
private :compute_cache_key
|
327
|
+
|
328
|
+
# Returns a cache version that can be used together with the cache key to form
|
329
|
+
# a recyclable caching scheme. The cache version is built with the number of records
|
330
|
+
# matching the query, and the timestamp of the last updated record. When a new record
|
331
|
+
# comes to match the query, or any of the existing records is updated or deleted,
|
332
|
+
# the cache version changes.
|
333
|
+
#
|
334
|
+
# If the collection is loaded, the method will iterate through the records
|
335
|
+
# to generate the timestamp, otherwise it will trigger one SQL query like:
|
336
|
+
#
|
337
|
+
# SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
|
338
|
+
def cache_version(timestamp_column = :updated_at)
|
339
|
+
if collection_cache_versioning
|
340
|
+
@cache_versions ||= {}
|
341
|
+
@cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def compute_cache_version(timestamp_column) # :nodoc:
|
346
|
+
if loaded? || distinct_value
|
347
|
+
size = records.size
|
348
|
+
if size > 0
|
349
|
+
timestamp = max_by(×tamp_column)._read_attribute(timestamp_column)
|
350
|
+
end
|
351
|
+
else
|
352
|
+
collection = eager_loading? ? apply_join_dependency : self
|
353
|
+
|
354
|
+
column = connection.visitor.compile(arel_attribute(timestamp_column))
|
355
|
+
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
356
|
+
|
357
|
+
if collection.has_limit_or_offset?
|
358
|
+
query = collection.select("#{column} AS collection_cache_key_timestamp")
|
359
|
+
subquery_alias = "subquery_for_cache_key"
|
360
|
+
subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
|
361
|
+
arel = query.build_subquery(subquery_alias, select_values % subquery_column)
|
362
|
+
else
|
363
|
+
query = collection.unscope(:order)
|
364
|
+
query.select_values = [select_values % column]
|
365
|
+
arel = query.arel
|
366
|
+
end
|
367
|
+
|
368
|
+
result = connection.select_one(arel, nil)
|
369
|
+
|
370
|
+
if result
|
371
|
+
column_type = klass.type_for_attribute(timestamp_column)
|
372
|
+
timestamp = column_type.deserialize(result["timestamp"])
|
373
|
+
size = result["size"]
|
374
|
+
else
|
375
|
+
timestamp = nil
|
376
|
+
size = 0
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
if timestamp
|
381
|
+
"#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}"
|
382
|
+
else
|
383
|
+
"#{size}"
|
384
|
+
end
|
303
385
|
end
|
386
|
+
private :compute_cache_version
|
304
387
|
|
305
388
|
# Scope all queries to the current scope.
|
306
389
|
#
|
@@ -312,12 +395,12 @@ module ActiveRecord
|
|
312
395
|
# Please check unscoped if you want to remove all previous scopes (including
|
313
396
|
# the default_scope) during the execution of a block.
|
314
397
|
def scoping
|
315
|
-
|
398
|
+
already_in_scope? ? yield : _scoping(self) { yield }
|
316
399
|
end
|
317
400
|
|
318
|
-
def _exec_scope(*args, &block) # :nodoc:
|
401
|
+
def _exec_scope(name, *args, &block) # :nodoc:
|
319
402
|
@delegate_to_klass = true
|
320
|
-
instance_exec(*args, &block) || self
|
403
|
+
_scoping(_deprecated_spawn(name)) { instance_exec(*args, &block) || self }
|
321
404
|
ensure
|
322
405
|
@delegate_to_klass = false
|
323
406
|
end
|
@@ -327,6 +410,8 @@ module ActiveRecord
|
|
327
410
|
# trigger Active Record callbacks or validations. However, values passed to #update_all will still go through
|
328
411
|
# Active Record's normal type casting and serialization.
|
329
412
|
#
|
413
|
+
# Note: As Active Record callbacks are not triggered, this method will not automatically update +updated_at+/+updated_on+ columns.
|
414
|
+
#
|
330
415
|
# ==== Parameters
|
331
416
|
#
|
332
417
|
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
|
@@ -361,6 +446,12 @@ module ActiveRecord
|
|
361
446
|
stmt.wheres = arel.constraints
|
362
447
|
|
363
448
|
if updates.is_a?(Hash)
|
449
|
+
if klass.locking_enabled? &&
|
450
|
+
!updates.key?(klass.locking_column) &&
|
451
|
+
!updates.key?(klass.locking_column.to_sym)
|
452
|
+
attr = arel_attribute(klass.locking_column)
|
453
|
+
updates[attr.name] = _increment_attribute(attr)
|
454
|
+
end
|
364
455
|
stmt.set _substitute_values(updates)
|
365
456
|
else
|
366
457
|
stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
|
@@ -383,10 +474,7 @@ module ActiveRecord
|
|
383
474
|
updates = {}
|
384
475
|
counters.each do |counter_name, value|
|
385
476
|
attr = arel_attribute(counter_name)
|
386
|
-
|
387
|
-
expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attr), 0)
|
388
|
-
expr = value < 0 ? expr - bind : expr + bind
|
389
|
-
updates[counter_name] = expr.expr
|
477
|
+
updates[attr.name] = _increment_attribute(attr, value)
|
390
478
|
end
|
391
479
|
|
392
480
|
if touch
|
@@ -398,10 +486,10 @@ module ActiveRecord
|
|
398
486
|
update_all updates
|
399
487
|
end
|
400
488
|
|
401
|
-
# Touches all records in the current relation without instantiating records first with the updated_at
|
489
|
+
# Touches all records in the current relation without instantiating records first with the +updated_at+/+updated_on+ attributes
|
402
490
|
# set to the current time or the time specified.
|
403
491
|
# This method can be passed attribute names and an optional time argument.
|
404
|
-
# If attribute names are passed, they are updated along with updated_at
|
492
|
+
# If attribute names are passed, they are updated along with +updated_at+/+updated_on+ attributes.
|
405
493
|
# If no time argument is passed, the current time is used as default.
|
406
494
|
#
|
407
495
|
# === Examples
|
@@ -422,12 +510,7 @@ module ActiveRecord
|
|
422
510
|
# Person.where(name: 'David').touch_all
|
423
511
|
# # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
|
424
512
|
def touch_all(*names, time: nil)
|
425
|
-
|
426
|
-
names << { time: time }
|
427
|
-
update_counters(klass.locking_column => 1, touch: names)
|
428
|
-
else
|
429
|
-
update_all klass.touch_attributes_with_time(*names, time: time)
|
430
|
-
end
|
513
|
+
update_all klass.touch_attributes_with_time(*names, time: time)
|
431
514
|
end
|
432
515
|
|
433
516
|
# Destroys the records by instantiating each
|
@@ -470,8 +553,8 @@ module ActiveRecord
|
|
470
553
|
# # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
|
471
554
|
def delete_all
|
472
555
|
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
|
473
|
-
value =
|
474
|
-
|
556
|
+
value = @values[method]
|
557
|
+
method == :distinct ? value : value&.any?
|
475
558
|
end
|
476
559
|
if invalid_methods.any?
|
477
560
|
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
|
@@ -496,6 +579,32 @@ module ActiveRecord
|
|
496
579
|
affected
|
497
580
|
end
|
498
581
|
|
582
|
+
# Finds and destroys all records matching the specified conditions.
|
583
|
+
# This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
|
584
|
+
# Returns the collection of objects that were destroyed.
|
585
|
+
#
|
586
|
+
# If no record is found, returns empty array.
|
587
|
+
#
|
588
|
+
# Person.destroy_by(id: 13)
|
589
|
+
# Person.destroy_by(name: 'Spartacus', rating: 4)
|
590
|
+
# Person.destroy_by("published_at < ?", 2.weeks.ago)
|
591
|
+
def destroy_by(*args)
|
592
|
+
where(*args).destroy_all
|
593
|
+
end
|
594
|
+
|
595
|
+
# Finds and deletes all records matching the specified conditions.
|
596
|
+
# This is short-hand for <tt>relation.where(condition).delete_all</tt>.
|
597
|
+
# Returns the number of rows affected.
|
598
|
+
#
|
599
|
+
# If no record is found, returns <tt>0</tt> as zero rows were affected.
|
600
|
+
#
|
601
|
+
# Person.delete_by(id: 13)
|
602
|
+
# Person.delete_by(name: 'Spartacus', rating: 4)
|
603
|
+
# Person.delete_by("published_at < ?", 2.weeks.ago)
|
604
|
+
def delete_by(*args)
|
605
|
+
where(*args).delete_all
|
606
|
+
end
|
607
|
+
|
499
608
|
# Causes the records to be loaded from the database if they have not
|
500
609
|
# been loaded already. You can use this if for some reason you need
|
501
610
|
# to explicitly load some records before actually using them. The
|
@@ -516,6 +625,7 @@ module ActiveRecord
|
|
516
625
|
|
517
626
|
def reset
|
518
627
|
@delegate_to_klass = false
|
628
|
+
@_deprecated_scope_source = nil
|
519
629
|
@to_sql = @arel = @loaded = @should_eager_load = nil
|
520
630
|
@records = [].freeze
|
521
631
|
@offsets = {}
|
@@ -624,14 +734,46 @@ module ActiveRecord
|
|
624
734
|
end
|
625
735
|
end
|
626
736
|
|
737
|
+
attr_reader :_deprecated_scope_source # :nodoc:
|
738
|
+
|
627
739
|
protected
|
740
|
+
attr_writer :_deprecated_scope_source # :nodoc:
|
628
741
|
|
629
742
|
def load_records(records)
|
630
743
|
@records = records.freeze
|
631
744
|
@loaded = true
|
632
745
|
end
|
633
746
|
|
747
|
+
def null_relation? # :nodoc:
|
748
|
+
is_a?(NullRelation)
|
749
|
+
end
|
750
|
+
|
634
751
|
private
|
752
|
+
def already_in_scope?
|
753
|
+
@delegate_to_klass && begin
|
754
|
+
scope = klass.current_scope(true)
|
755
|
+
scope && !scope._deprecated_scope_source
|
756
|
+
end
|
757
|
+
end
|
758
|
+
|
759
|
+
def _deprecated_spawn(name)
|
760
|
+
spawn.tap { |scope| scope._deprecated_scope_source = name }
|
761
|
+
end
|
762
|
+
|
763
|
+
def _deprecated_scope_block(name, &block)
|
764
|
+
-> record do
|
765
|
+
klass.current_scope = _deprecated_spawn(name)
|
766
|
+
yield record if block_given?
|
767
|
+
end
|
768
|
+
end
|
769
|
+
|
770
|
+
def _scoping(scope)
|
771
|
+
previous, klass.current_scope = klass.current_scope(true), scope
|
772
|
+
yield
|
773
|
+
ensure
|
774
|
+
klass.current_scope = previous
|
775
|
+
end
|
776
|
+
|
635
777
|
def _substitute_values(values)
|
636
778
|
values.map do |name, value|
|
637
779
|
attr = arel_attribute(name)
|
@@ -643,12 +785,19 @@ module ActiveRecord
|
|
643
785
|
end
|
644
786
|
end
|
645
787
|
|
788
|
+
def _increment_attribute(attribute, value = 1)
|
789
|
+
bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
|
790
|
+
expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
|
791
|
+
expr = value < 0 ? expr - bind : expr + bind
|
792
|
+
expr.expr
|
793
|
+
end
|
794
|
+
|
646
795
|
def exec_queries(&block)
|
647
796
|
skip_query_cache_if_necessary do
|
648
797
|
@records =
|
649
798
|
if eager_loading?
|
650
799
|
apply_join_dependency do |relation, join_dependency|
|
651
|
-
if
|
800
|
+
if relation.null_relation?
|
652
801
|
[]
|
653
802
|
else
|
654
803
|
relation = join_dependency.apply_column_aliases(relation)
|
@@ -61,8 +61,9 @@ module ActiveRecord
|
|
61
61
|
# # => "id ASC"
|
62
62
|
def sanitize_sql_for_order(condition)
|
63
63
|
if condition.is_a?(Array) && condition.first.to_s.include?("?")
|
64
|
-
disallow_raw_sql!(
|
65
|
-
|
64
|
+
disallow_raw_sql!(
|
65
|
+
[condition.first],
|
66
|
+
permit: connection.column_name_with_order_matcher
|
66
67
|
)
|
67
68
|
|
68
69
|
# Ensure we aren't dealing with a subclass of String that might
|
@@ -133,6 +134,33 @@ module ActiveRecord
|
|
133
134
|
end
|
134
135
|
end
|
135
136
|
|
137
|
+
def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
|
138
|
+
unexpected = nil
|
139
|
+
args.each do |arg|
|
140
|
+
next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s)
|
141
|
+
(unexpected ||= []) << arg
|
142
|
+
end
|
143
|
+
|
144
|
+
return unless unexpected
|
145
|
+
|
146
|
+
if allow_unsafe_raw_sql == :deprecated
|
147
|
+
ActiveSupport::Deprecation.warn(
|
148
|
+
"Dangerous query method (method whose arguments are used as raw " \
|
149
|
+
"SQL) called with non-attribute argument(s): " \
|
150
|
+
"#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
|
151
|
+
"arguments will be disallowed in Rails 6.1. This method should " \
|
152
|
+
"not be called with user-provided values, such as request " \
|
153
|
+
"parameters or model attributes. Known-safe values can be passed " \
|
154
|
+
"by wrapping them in Arel.sql()."
|
155
|
+
)
|
156
|
+
else
|
157
|
+
raise(ActiveRecord::UnknownAttributeReference,
|
158
|
+
"Query method called with non-attribute argument(s): " +
|
159
|
+
unexpected.map(&:inspect).join(", ")
|
160
|
+
)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
136
164
|
private
|
137
165
|
def replace_bind_variables(statement, values)
|
138
166
|
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
|
@@ -165,10 +193,11 @@ module ActiveRecord
|
|
165
193
|
|
166
194
|
def quote_bound_value(value, c = connection)
|
167
195
|
if value.respond_to?(:map) && !value.acts_like?(:string)
|
168
|
-
|
196
|
+
quoted = value.map { |v| c.quote(v) }
|
197
|
+
if quoted.empty?
|
169
198
|
c.quote(nil)
|
170
199
|
else
|
171
|
-
|
200
|
+
quoted.join(",")
|
172
201
|
end
|
173
202
|
else
|
174
203
|
c.quote(value)
|
data/lib/active_record/schema.rb
CHANGED
@@ -47,6 +47,7 @@ module ActiveRecord
|
|
47
47
|
end
|
48
48
|
|
49
49
|
private
|
50
|
+
attr_accessor :table_name
|
50
51
|
|
51
52
|
def initialize(connection, options = {})
|
52
53
|
@connection = connection
|
@@ -110,6 +111,8 @@ HEADER
|
|
110
111
|
def table(table, stream)
|
111
112
|
columns = @connection.columns(table)
|
112
113
|
begin
|
114
|
+
self.table_name = table
|
115
|
+
|
113
116
|
tbl = StringIO.new
|
114
117
|
|
115
118
|
# first dump primary key column
|
@@ -143,7 +146,11 @@ HEADER
|
|
143
146
|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
144
147
|
next if column.name == pk
|
145
148
|
type, colspec = column_spec(column)
|
146
|
-
|
149
|
+
if type.is_a?(Symbol)
|
150
|
+
tbl.print " t.#{type} #{column.name.inspect}"
|
151
|
+
else
|
152
|
+
tbl.print " t.column #{column.name.inspect}, #{type.inspect}"
|
153
|
+
end
|
147
154
|
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
|
148
155
|
tbl.puts
|
149
156
|
end
|
@@ -159,6 +166,8 @@ HEADER
|
|
159
166
|
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
160
167
|
stream.puts "# #{e.message}"
|
161
168
|
stream.puts
|
169
|
+
ensure
|
170
|
+
self.table_name = nil
|
162
171
|
end
|
163
172
|
end
|
164
173
|
|
@@ -31,14 +31,7 @@ module ActiveRecord
|
|
31
31
|
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
32
32
|
# }
|
33
33
|
def unscoped
|
34
|
-
block_given? ?
|
35
|
-
end
|
36
|
-
|
37
|
-
def _scoping(relation) # :nodoc:
|
38
|
-
previous, self.current_scope = current_scope(true), relation
|
39
|
-
yield
|
40
|
-
ensure
|
41
|
-
self.current_scope = previous
|
34
|
+
block_given? ? relation.scoping { yield } : relation
|
42
35
|
end
|
43
36
|
|
44
37
|
# Are there attributes associated with this scope?
|
@@ -93,8 +86,8 @@ module ActiveRecord
|
|
93
86
|
# # Should return a scope, you can call 'super' here etc.
|
94
87
|
# end
|
95
88
|
# end
|
96
|
-
def default_scope(scope = nil) # :doc:
|
97
|
-
scope =
|
89
|
+
def default_scope(scope = nil, &block) # :doc:
|
90
|
+
scope = block if block_given?
|
98
91
|
|
99
92
|
if scope.is_a?(Relation) || !scope.respond_to?(:call)
|
100
93
|
raise ArgumentError,
|
@@ -107,7 +100,7 @@ module ActiveRecord
|
|
107
100
|
self.default_scopes += [scope]
|
108
101
|
end
|
109
102
|
|
110
|
-
def build_default_scope(
|
103
|
+
def build_default_scope(relation = relation())
|
111
104
|
return if abstract_class?
|
112
105
|
|
113
106
|
if default_scope_override.nil?
|
@@ -118,15 +111,14 @@ module ActiveRecord
|
|
118
111
|
# The user has defined their own default scope method, so call that
|
119
112
|
evaluate_default_scope do
|
120
113
|
if scope = default_scope
|
121
|
-
|
114
|
+
relation.merge!(scope)
|
122
115
|
end
|
123
116
|
end
|
124
117
|
elsif default_scopes.any?
|
125
|
-
base_rel ||= relation
|
126
118
|
evaluate_default_scope do
|
127
|
-
default_scopes.inject(
|
119
|
+
default_scopes.inject(relation) do |default_scope, scope|
|
128
120
|
scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
|
129
|
-
default_scope.
|
121
|
+
default_scope.instance_exec(&scope) || default_scope
|
130
122
|
end
|
131
123
|
end
|
132
124
|
end
|
@@ -27,6 +27,14 @@ module ActiveRecord
|
|
27
27
|
scope = current_scope
|
28
28
|
|
29
29
|
if scope
|
30
|
+
if scope._deprecated_scope_source
|
31
|
+
ActiveSupport::Deprecation.warn(<<~MSG.squish)
|
32
|
+
Class level methods will no longer inherit scoping from `#{scope._deprecated_scope_source}`
|
33
|
+
in Rails 6.1. To continue using the scoped relation, pass it into the block directly.
|
34
|
+
To instead access the full set of models, as Rails 6.1 will, use `#{name}.unscoped`.
|
35
|
+
MSG
|
36
|
+
end
|
37
|
+
|
30
38
|
if self == scope.klass
|
31
39
|
scope.clone
|
32
40
|
else
|
@@ -50,7 +58,7 @@ module ActiveRecord
|
|
50
58
|
end
|
51
59
|
|
52
60
|
def default_extensions # :nodoc:
|
53
|
-
if scope =
|
61
|
+
if scope = scope_for_association || build_default_scope
|
54
62
|
scope.extensions
|
55
63
|
else
|
56
64
|
[]
|
@@ -180,7 +188,7 @@ module ActiveRecord
|
|
180
188
|
|
181
189
|
if body.respond_to?(:to_proc)
|
182
190
|
singleton_class.define_method(name) do |*args|
|
183
|
-
scope = all._exec_scope(*args, &body)
|
191
|
+
scope = all._exec_scope(name, *args, &body)
|
184
192
|
scope = scope.extending(extension) if extension
|
185
193
|
scope
|
186
194
|
end
|
@@ -23,14 +23,13 @@ module ActiveRecord
|
|
23
23
|
current_scope
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
26
|
+
def current_scope(skip_inherited_scope = false)
|
27
|
+
ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
|
28
|
+
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
def current_scope=(scope)
|
31
|
+
ScopeRegistry.set_value_for(:current_scope, self, scope)
|
32
|
+
end
|
34
33
|
end
|
35
34
|
|
36
35
|
def populate_with_current_scope_attributes # :nodoc:
|
@@ -113,8 +113,8 @@ module ActiveRecord
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
-
def self.create(connection,
|
117
|
-
relation = block.call Params.new
|
116
|
+
def self.create(connection, callable = nil, &block)
|
117
|
+
relation = (callable || block).call Params.new
|
118
118
|
query_builder, binds = connection.cacheable_query(self, relation.arel)
|
119
119
|
bind_map = BindMap.new(binds)
|
120
120
|
new(query_builder, bind_map, relation.klass)
|
data/lib/active_record/store.rb
CHANGED
@@ -11,6 +11,12 @@ module ActiveRecord
|
|
11
11
|
# of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
|
12
12
|
# already built around just accessing attributes on the model.
|
13
13
|
#
|
14
|
+
# Every accessor comes with dirty tracking methods (+key_changed?+, +key_was+ and +key_change+) and
|
15
|
+
# methods to access the changes made during the last save (+saved_change_to_key?+, +saved_change_to_key+ and
|
16
|
+
# +key_before_last_save+).
|
17
|
+
#
|
18
|
+
# NOTE: There is no +key_will_change!+ method for accessors, use +store_will_change!+ instead.
|
19
|
+
#
|
14
20
|
# Make sure that you declare the database column used for the serialized store as a text, so there's
|
15
21
|
# plenty of room.
|
16
22
|
#
|
@@ -49,6 +55,12 @@ module ActiveRecord
|
|
49
55
|
# u.settings[:country] # => 'Denmark'
|
50
56
|
# u.settings['country'] # => 'Denmark'
|
51
57
|
#
|
58
|
+
# # Dirty tracking
|
59
|
+
# u.color = 'green'
|
60
|
+
# u.color_changed? # => true
|
61
|
+
# u.color_was # => 'black'
|
62
|
+
# u.color_change # => ['black', 'red']
|
63
|
+
#
|
52
64
|
# # Add additional accessors to an existing store through store_accessor
|
53
65
|
# class SuperUser < User
|
54
66
|
# store_accessor :settings, :privileges, :servants
|
@@ -127,6 +139,42 @@ module ActiveRecord
|
|
127
139
|
define_method(accessor_key) do
|
128
140
|
read_store_attribute(store_attribute, key)
|
129
141
|
end
|
142
|
+
|
143
|
+
define_method("#{accessor_key}_changed?") do
|
144
|
+
return false unless attribute_changed?(store_attribute)
|
145
|
+
prev_store, new_store = changes[store_attribute]
|
146
|
+
prev_store&.dig(key) != new_store&.dig(key)
|
147
|
+
end
|
148
|
+
|
149
|
+
define_method("#{accessor_key}_change") do
|
150
|
+
return unless attribute_changed?(store_attribute)
|
151
|
+
prev_store, new_store = changes[store_attribute]
|
152
|
+
[prev_store&.dig(key), new_store&.dig(key)]
|
153
|
+
end
|
154
|
+
|
155
|
+
define_method("#{accessor_key}_was") do
|
156
|
+
return unless attribute_changed?(store_attribute)
|
157
|
+
prev_store, _new_store = changes[store_attribute]
|
158
|
+
prev_store&.dig(key)
|
159
|
+
end
|
160
|
+
|
161
|
+
define_method("saved_change_to_#{accessor_key}?") do
|
162
|
+
return false unless saved_change_to_attribute?(store_attribute)
|
163
|
+
prev_store, new_store = saved_change_to_attribute(store_attribute)
|
164
|
+
prev_store&.dig(key) != new_store&.dig(key)
|
165
|
+
end
|
166
|
+
|
167
|
+
define_method("saved_change_to_#{accessor_key}") do
|
168
|
+
return unless saved_change_to_attribute?(store_attribute)
|
169
|
+
prev_store, new_store = saved_change_to_attribute(store_attribute)
|
170
|
+
[prev_store&.dig(key), new_store&.dig(key)]
|
171
|
+
end
|
172
|
+
|
173
|
+
define_method("#{accessor_key}_before_last_save") do
|
174
|
+
return unless saved_change_to_attribute?(store_attribute)
|
175
|
+
prev_store, _new_store = saved_change_to_attribute(store_attribute)
|
176
|
+
prev_store&.dig(key)
|
177
|
+
end
|
130
178
|
end
|
131
179
|
end
|
132
180
|
|