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.

Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +529 -10
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +7 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +27 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +5 -6
  16. data/lib/active_record/associations/collection_proxy.rb +13 -42
  17. data/lib/active_record/associations/has_many_association.rb +1 -9
  18. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  19. data/lib/active_record/associations/join_dependency.rb +14 -9
  20. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  21. data/lib/active_record/associations/preloader.rb +12 -7
  22. data/lib/active_record/associations/preloader/association.rb +37 -34
  23. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  24. data/lib/active_record/attribute_methods.rb +3 -53
  25. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  26. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  27. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  28. data/lib/active_record/attribute_methods/query.rb +2 -3
  29. data/lib/active_record/attribute_methods/read.rb +3 -9
  30. data/lib/active_record/attribute_methods/write.rb +6 -12
  31. data/lib/active_record/attributes.rb +13 -0
  32. data/lib/active_record/autosave_association.rb +21 -7
  33. data/lib/active_record/base.rb +0 -1
  34. data/lib/active_record/callbacks.rb +3 -3
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
  36. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  37. data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
  38. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
  39. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  40. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  42. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  44. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  45. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
  46. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
  47. data/lib/active_record/connection_adapters/column.rb +17 -13
  48. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  49. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  52. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  53. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  54. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  55. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  56. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  59. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  60. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  62. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  63. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  66. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
  68. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  69. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  70. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  71. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
  74. data/lib/active_record/connection_handling.rb +40 -17
  75. data/lib/active_record/core.rb +35 -24
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  78. data/lib/active_record/database_configurations/url_config.rb +21 -16
  79. data/lib/active_record/dynamic_matchers.rb +1 -1
  80. data/lib/active_record/enum.rb +15 -0
  81. data/lib/active_record/errors.rb +18 -13
  82. data/lib/active_record/fixtures.rb +11 -6
  83. data/lib/active_record/gem_version.rb +2 -2
  84. data/lib/active_record/inheritance.rb +1 -1
  85. data/lib/active_record/insert_all.rb +179 -0
  86. data/lib/active_record/integration.rb +13 -1
  87. data/lib/active_record/internal_metadata.rb +5 -1
  88. data/lib/active_record/locking/optimistic.rb +3 -4
  89. data/lib/active_record/log_subscriber.rb +1 -1
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  92. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/migration/command_recorder.rb +28 -14
  95. data/lib/active_record/migration/compatibility.rb +72 -63
  96. data/lib/active_record/model_schema.rb +3 -0
  97. data/lib/active_record/persistence.rb +212 -19
  98. data/lib/active_record/querying.rb +18 -14
  99. data/lib/active_record/railtie.rb +9 -1
  100. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  101. data/lib/active_record/railties/databases.rake +124 -25
  102. data/lib/active_record/reflection.rb +18 -32
  103. data/lib/active_record/relation.rb +185 -35
  104. data/lib/active_record/relation/calculations.rb +40 -44
  105. data/lib/active_record/relation/delegation.rb +23 -31
  106. data/lib/active_record/relation/finder_methods.rb +23 -14
  107. data/lib/active_record/relation/merger.rb +11 -16
  108. data/lib/active_record/relation/query_attribute.rb +5 -3
  109. data/lib/active_record/relation/query_methods.rb +230 -69
  110. data/lib/active_record/relation/spawn_methods.rb +1 -1
  111. data/lib/active_record/relation/where_clause.rb +10 -10
  112. data/lib/active_record/sanitization.rb +33 -4
  113. data/lib/active_record/schema.rb +1 -1
  114. data/lib/active_record/schema_dumper.rb +10 -1
  115. data/lib/active_record/schema_migration.rb +1 -1
  116. data/lib/active_record/scoping.rb +6 -7
  117. data/lib/active_record/scoping/default.rb +7 -15
  118. data/lib/active_record/scoping/named.rb +10 -2
  119. data/lib/active_record/statement_cache.rb +2 -2
  120. data/lib/active_record/store.rb +48 -0
  121. data/lib/active_record/table_metadata.rb +9 -13
  122. data/lib/active_record/tasks/database_tasks.rb +109 -6
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  124. data/lib/active_record/test_databases.rb +1 -16
  125. data/lib/active_record/test_fixtures.rb +2 -2
  126. data/lib/active_record/timestamp.rb +35 -19
  127. data/lib/active_record/touch_later.rb +4 -2
  128. data/lib/active_record/transactions.rb +56 -46
  129. data/lib/active_record/type_caster/connection.rb +16 -10
  130. data/lib/active_record/validations.rb +1 -0
  131. data/lib/active_record/validations/uniqueness.rb +4 -4
  132. data/lib/arel.rb +18 -4
  133. data/lib/arel/insert_manager.rb +3 -3
  134. data/lib/arel/nodes.rb +2 -1
  135. data/lib/arel/nodes/and.rb +1 -1
  136. data/lib/arel/nodes/case.rb +1 -1
  137. data/lib/arel/nodes/comment.rb +29 -0
  138. data/lib/arel/nodes/select_core.rb +16 -12
  139. data/lib/arel/nodes/unary.rb +1 -0
  140. data/lib/arel/nodes/values_list.rb +2 -17
  141. data/lib/arel/select_manager.rb +10 -10
  142. data/lib/arel/visitors/depth_first.rb +7 -2
  143. data/lib/arel/visitors/dot.rb +7 -2
  144. data/lib/arel/visitors/ibm_db.rb +13 -0
  145. data/lib/arel/visitors/informix.rb +6 -0
  146. data/lib/arel/visitors/mssql.rb +15 -1
  147. data/lib/arel/visitors/oracle12.rb +4 -5
  148. data/lib/arel/visitors/postgresql.rb +4 -10
  149. data/lib/arel/visitors/to_sql.rb +107 -131
  150. data/lib/arel/visitors/visitor.rb +9 -5
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  152. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  154. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  155. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  156. metadata +19 -12
  157. data/lib/active_record/collection_cache_key.rb +0 -53
  158. 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
- scoping { klass.create(attributes, &block) }
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
- scoping { klass.create!(attributes, &block) }
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 the records fetched by
280
- # this query. The cache key is built with a fingerprint of the sql query,
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
- # Product.where("name like ?", "%Cosmic Encounter%").cache_key
286
- # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
297
+ # Product.where("name like ?", "%Cosmic Encounter%").cache_key
298
+ # # => "products/query-1850ab3d302391b85b8693e941286659"
287
299
  #
288
- # If the collection is loaded, the method will iterate through the records
289
- # to generate the timestamp, otherwise it will trigger one SQL query like:
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
- # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
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] ||= @klass.collection_cache_key(self, 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(&timestamp_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
- @delegate_to_klass ? yield : klass._scoping(self) { yield }
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
- bind = predicate_builder.build_bind_attribute(attr.name, value.abs)
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/on attributes
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/on attributes.
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
- if klass.locking_enabled?
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 = get_value(method)
474
- SINGLE_VALUE_METHODS.include?(method) ? value : value.any?
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 ActiveRecord::NullRelation === relation
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
- relation.distinct!
133
- # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
134
- if (column_name == :all || column_name.nil?) && select_values.empty?
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.map { |cn|
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 && (group_values.any? || select_values.empty? && order_values.empty?)
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 column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
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
- if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
260
- @klass.arel_attribute(column_name)
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
- group_attrs = group_values
309
+ group_fields = group_values
308
310
 
309
- if group_attrs.first.respond_to?(:to_sym)
310
- association = @klass._reflect_on_association(group_attrs.first)
311
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
312
- group_fields = Array(associated ? association.foreign_key : group_attrs)
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| column_alias_for(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
- if operation == "count" && column_name == :all
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 keys to the value that the database adapter returns as
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(keys)
378
- if keys.respond_to? :name
379
- keys = "#{keys.relation.name}.#{keys.name}"
380
- end
381
-
382
- table_name = keys.to_s.downcase
383
- table_name.gsub!(/\*/, "all")
384
- table_name.gsub!(/\W+/, " ")
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
- subquery = relation.arel.as(Arel.sql("subquery_for_count"))
423
- select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
418
+ subquery_alias = Arel.sql("subquery_for_count")
419
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
424
420
 
425
- Arel::SelectManager.new(subquery).project(select_value)
421
+ relation.build_subquery(subquery_alias, select_value)
426
422
  end
427
423
  end
428
424
  end