activerecord 6.0.0.beta1 → 6.0.1.rc1
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 +529 -10
- data/README.rdoc +3 -1
- data/lib/active_record.rb +7 -1
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +4 -3
- data/lib/active_record/associations/association.rb +27 -2
- 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.rb +14 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/preloader.rb +12 -7
- 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/attribute_methods.rb +3 -53
- 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/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +21 -7
- 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 +134 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -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 +115 -35
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
- 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 +8 -2
- 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 +95 -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 +120 -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 +73 -118
- data/lib/active_record/connection_handling.rb +40 -17
- data/lib/active_record/core.rb +35 -24
- data/lib/active_record/database_configurations.rb +99 -50
- 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/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 +2 -2
- 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.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/model_schema.rb +3 -0
- 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.rb +185 -35
- 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 +23 -14
- 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 +230 -69
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- 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.rb +6 -7
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- 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 +56 -46
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/arel.rb +18 -4
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- 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/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/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 +19 -12
- 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,9 +625,11 @@ 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 = {}
|
632
|
+
@take = nil
|
522
633
|
self
|
523
634
|
end
|
524
635
|
|
@@ -624,14 +735,46 @@ module ActiveRecord
|
|
624
735
|
end
|
625
736
|
end
|
626
737
|
|
738
|
+
attr_reader :_deprecated_scope_source # :nodoc:
|
739
|
+
|
627
740
|
protected
|
741
|
+
attr_writer :_deprecated_scope_source # :nodoc:
|
628
742
|
|
629
743
|
def load_records(records)
|
630
744
|
@records = records.freeze
|
631
745
|
@loaded = true
|
632
746
|
end
|
633
747
|
|
748
|
+
def null_relation? # :nodoc:
|
749
|
+
is_a?(NullRelation)
|
750
|
+
end
|
751
|
+
|
634
752
|
private
|
753
|
+
def already_in_scope?
|
754
|
+
@delegate_to_klass && begin
|
755
|
+
scope = klass.current_scope(true)
|
756
|
+
scope && !scope._deprecated_scope_source
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
def _deprecated_spawn(name)
|
761
|
+
spawn.tap { |scope| scope._deprecated_scope_source = name }
|
762
|
+
end
|
763
|
+
|
764
|
+
def _deprecated_scope_block(name, &block)
|
765
|
+
-> record do
|
766
|
+
klass.current_scope = _deprecated_spawn(name)
|
767
|
+
yield record if block_given?
|
768
|
+
end
|
769
|
+
end
|
770
|
+
|
771
|
+
def _scoping(scope)
|
772
|
+
previous, klass.current_scope = klass.current_scope(true), scope
|
773
|
+
yield
|
774
|
+
ensure
|
775
|
+
klass.current_scope = previous
|
776
|
+
end
|
777
|
+
|
635
778
|
def _substitute_values(values)
|
636
779
|
values.map do |name, value|
|
637
780
|
attr = arel_attribute(name)
|
@@ -643,12 +786,19 @@ module ActiveRecord
|
|
643
786
|
end
|
644
787
|
end
|
645
788
|
|
789
|
+
def _increment_attribute(attribute, value = 1)
|
790
|
+
bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
|
791
|
+
expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
|
792
|
+
expr = value < 0 ? expr - bind : expr + bind
|
793
|
+
expr.expr
|
794
|
+
end
|
795
|
+
|
646
796
|
def exec_queries(&block)
|
647
797
|
skip_query_cache_if_necessary do
|
648
798
|
@records =
|
649
799
|
if eager_loading?
|
650
800
|
apply_join_dependency do |relation, join_dependency|
|
651
|
-
if
|
801
|
+
if relation.null_relation?
|
652
802
|
[]
|
653
803
|
else
|
654
804
|
relation = join_dependency.apply_column_aliases(relation)
|
@@ -129,11 +129,12 @@ module ActiveRecord
|
|
129
129
|
relation = apply_join_dependency
|
130
130
|
|
131
131
|
if operation.to_s.downcase == "count"
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
relation.order_values = []
|
132
|
+
unless distinct_value || distinct_select?(column_name || select_for_count)
|
133
|
+
relation.distinct!
|
134
|
+
relation.select_values = [ klass.primary_key || table[Arel.star] ]
|
136
135
|
end
|
136
|
+
# PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
|
137
|
+
relation.order_values = []
|
137
138
|
end
|
138
139
|
|
139
140
|
relation.calculate(operation, column_name)
|
@@ -186,11 +187,9 @@ module ActiveRecord
|
|
186
187
|
relation = apply_join_dependency
|
187
188
|
relation.pluck(*column_names)
|
188
189
|
else
|
189
|
-
disallow_raw_sql!(column_names)
|
190
|
+
klass.disallow_raw_sql!(column_names)
|
190
191
|
relation = spawn
|
191
|
-
relation.select_values = column_names
|
192
|
-
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
|
193
|
-
}
|
192
|
+
relation.select_values = column_names
|
194
193
|
result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) }
|
195
194
|
result.cast_values(klass.attribute_types)
|
196
195
|
end
|
@@ -223,7 +222,6 @@ module ActiveRecord
|
|
223
222
|
end
|
224
223
|
|
225
224
|
private
|
226
|
-
|
227
225
|
def has_include?(column_name)
|
228
226
|
eager_loading? || (includes_values.present? && column_name && column_name != :all)
|
229
227
|
end
|
@@ -238,10 +236,12 @@ module ActiveRecord
|
|
238
236
|
if operation == "count"
|
239
237
|
column_name ||= select_for_count
|
240
238
|
if column_name == :all
|
241
|
-
if distinct
|
239
|
+
if !distinct
|
240
|
+
distinct = distinct_select?(select_for_count) if group_values.empty?
|
241
|
+
elsif group_values.any? || select_values.empty? && order_values.empty?
|
242
242
|
column_name = primary_key
|
243
243
|
end
|
244
|
-
elsif
|
244
|
+
elsif distinct_select?(column_name)
|
245
245
|
distinct = nil
|
246
246
|
end
|
247
247
|
end
|
@@ -253,13 +253,15 @@ module ActiveRecord
|
|
253
253
|
end
|
254
254
|
end
|
255
255
|
|
256
|
+
def distinct_select?(column_name)
|
257
|
+
column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
|
258
|
+
end
|
259
|
+
|
256
260
|
def aggregate_column(column_name)
|
257
261
|
return column_name if Arel::Expressions === column_name
|
258
262
|
|
259
|
-
|
260
|
-
|
261
|
-
else
|
262
|
-
Arel.sql(column_name == :all ? "*" : column_name.to_s)
|
263
|
+
arel_column(column_name.to_s) do |name|
|
264
|
+
Arel.sql(column_name == :all ? "*" : name)
|
263
265
|
end
|
264
266
|
end
|
265
267
|
|
@@ -304,25 +306,22 @@ module ActiveRecord
|
|
304
306
|
end
|
305
307
|
|
306
308
|
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
|
307
|
-
|
309
|
+
group_fields = group_values
|
308
310
|
|
309
|
-
if
|
310
|
-
association =
|
311
|
-
associated =
|
312
|
-
group_fields = Array(
|
313
|
-
else
|
314
|
-
group_fields = group_attrs
|
311
|
+
if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
|
312
|
+
association = klass._reflect_on_association(group_fields.first)
|
313
|
+
associated = association && association.belongs_to? # only count belongs_to associations
|
314
|
+
group_fields = Array(association.foreign_key) if associated
|
315
315
|
end
|
316
316
|
group_fields = arel_columns(group_fields)
|
317
317
|
|
318
|
-
group_aliases = group_fields.map { |field|
|
318
|
+
group_aliases = group_fields.map { |field|
|
319
|
+
field = connection.visitor.compile(field) if Arel.arel_node?(field)
|
320
|
+
column_alias_for(field.to_s.downcase)
|
321
|
+
}
|
319
322
|
group_columns = group_aliases.zip(group_fields)
|
320
323
|
|
321
|
-
|
322
|
-
aggregate_alias = "count_all"
|
323
|
-
else
|
324
|
-
aggregate_alias = column_alias_for([operation, column_name].join(" "))
|
325
|
-
end
|
324
|
+
aggregate_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
|
326
325
|
|
327
326
|
select_values = [
|
328
327
|
operation_over_aggregate_column(
|
@@ -367,25 +366,21 @@ module ActiveRecord
|
|
367
366
|
end]
|
368
367
|
end
|
369
368
|
|
370
|
-
# Converts the given
|
369
|
+
# Converts the given field to the value that the database adapter returns as
|
371
370
|
# a usable column name:
|
372
371
|
#
|
373
372
|
# column_alias_for("users.id") # => "users_id"
|
374
373
|
# column_alias_for("sum(id)") # => "sum_id"
|
375
374
|
# column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
|
376
375
|
# column_alias_for("count(*)") # => "count_all"
|
377
|
-
def column_alias_for(
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
table_name.strip!
|
386
|
-
table_name.gsub!(/ +/, "_")
|
387
|
-
|
388
|
-
@klass.connection.table_alias_for(table_name)
|
376
|
+
def column_alias_for(field)
|
377
|
+
column_alias = +field
|
378
|
+
column_alias.gsub!(/\*/, "all")
|
379
|
+
column_alias.gsub!(/\W+/, " ")
|
380
|
+
column_alias.strip!
|
381
|
+
column_alias.gsub!(/ +/, "_")
|
382
|
+
|
383
|
+
connection.table_alias_for(column_alias)
|
389
384
|
end
|
390
385
|
|
391
386
|
def type_for(field, &block)
|
@@ -413,16 +408,17 @@ module ActiveRecord
|
|
413
408
|
|
414
409
|
def build_count_subquery(relation, column_name, distinct)
|
415
410
|
if column_name == :all
|
411
|
+
column_alias = Arel.star
|
416
412
|
relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
|
417
413
|
else
|
418
414
|
column_alias = Arel.sql("count_column")
|
419
415
|
relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
|
420
416
|
end
|
421
417
|
|
422
|
-
|
423
|
-
select_value = operation_over_aggregate_column(column_alias
|
418
|
+
subquery_alias = Arel.sql("subquery_for_count")
|
419
|
+
select_value = operation_over_aggregate_column(column_alias, "count", false)
|
424
420
|
|
425
|
-
|
421
|
+
relation.build_subquery(subquery_alias, select_value)
|
426
422
|
end
|
427
423
|
end
|
428
424
|
end
|