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.

Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +455 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record/associations/association.rb +18 -1
  5. data/lib/active_record/associations/builder/association.rb +14 -18
  6. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  7. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  8. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  9. data/lib/active_record/associations/builder/has_many.rb +2 -0
  10. data/lib/active_record/associations/builder/has_one.rb +35 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  12. data/lib/active_record/associations/collection_association.rb +5 -6
  13. data/lib/active_record/associations/collection_proxy.rb +13 -42
  14. data/lib/active_record/associations/has_many_association.rb +1 -9
  15. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  16. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  17. data/lib/active_record/associations/join_dependency.rb +10 -9
  18. data/lib/active_record/associations/preloader/association.rb +37 -34
  19. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  20. data/lib/active_record/associations/preloader.rb +11 -6
  21. data/lib/active_record/associations.rb +3 -2
  22. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  23. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  24. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  25. data/lib/active_record/attribute_methods/query.rb +2 -3
  26. data/lib/active_record/attribute_methods/read.rb +3 -9
  27. data/lib/active_record/attribute_methods/write.rb +6 -12
  28. data/lib/active_record/attribute_methods.rb +3 -53
  29. data/lib/active_record/attributes.rb +13 -0
  30. data/lib/active_record/autosave_association.rb +15 -5
  31. data/lib/active_record/base.rb +0 -1
  32. data/lib/active_record/callbacks.rb +3 -3
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +124 -23
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +101 -70
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +11 -5
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  39. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  40. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  42. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  43. data/lib/active_record/connection_adapters/abstract_adapter.rb +108 -39
  44. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +93 -134
  45. data/lib/active_record/connection_adapters/column.rb +17 -13
  46. data/lib/active_record/connection_adapters/connection_specification.rb +1 -1
  47. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  48. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  49. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  50. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  51. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  52. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  53. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  54. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  55. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  56. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  57. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  58. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  59. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  60. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  61. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  62. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  63. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  64. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  65. data/lib/active_record/connection_adapters/postgresql_adapter.rb +91 -24
  66. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  67. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  68. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  69. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  70. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  71. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +69 -118
  72. data/lib/active_record/connection_handling.rb +32 -16
  73. data/lib/active_record/core.rb +27 -20
  74. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  75. data/lib/active_record/database_configurations/url_config.rb +21 -16
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/dynamic_matchers.rb +1 -1
  78. data/lib/active_record/enum.rb +15 -0
  79. data/lib/active_record/errors.rb +18 -13
  80. data/lib/active_record/fixtures.rb +11 -6
  81. data/lib/active_record/gem_version.rb +1 -1
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +179 -0
  84. data/lib/active_record/integration.rb +13 -1
  85. data/lib/active_record/internal_metadata.rb +5 -1
  86. data/lib/active_record/locking/optimistic.rb +3 -4
  87. data/lib/active_record/log_subscriber.rb +1 -1
  88. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  89. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/migration/command_recorder.rb +28 -14
  92. data/lib/active_record/migration/compatibility.rb +72 -63
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/persistence.rb +212 -19
  95. data/lib/active_record/querying.rb +18 -14
  96. data/lib/active_record/railtie.rb +9 -1
  97. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  98. data/lib/active_record/railties/databases.rake +124 -25
  99. data/lib/active_record/reflection.rb +18 -32
  100. data/lib/active_record/relation/calculations.rb +40 -44
  101. data/lib/active_record/relation/delegation.rb +23 -31
  102. data/lib/active_record/relation/finder_methods.rb +13 -13
  103. data/lib/active_record/relation/merger.rb +11 -16
  104. data/lib/active_record/relation/query_attribute.rb +5 -3
  105. data/lib/active_record/relation/query_methods.rb +217 -68
  106. data/lib/active_record/relation/spawn_methods.rb +1 -1
  107. data/lib/active_record/relation/where_clause.rb +10 -10
  108. data/lib/active_record/relation.rb +184 -35
  109. data/lib/active_record/sanitization.rb +33 -4
  110. data/lib/active_record/schema.rb +1 -1
  111. data/lib/active_record/schema_dumper.rb +10 -1
  112. data/lib/active_record/schema_migration.rb +1 -1
  113. data/lib/active_record/scoping/default.rb +7 -15
  114. data/lib/active_record/scoping/named.rb +10 -2
  115. data/lib/active_record/scoping.rb +6 -7
  116. data/lib/active_record/statement_cache.rb +2 -2
  117. data/lib/active_record/store.rb +48 -0
  118. data/lib/active_record/table_metadata.rb +9 -13
  119. data/lib/active_record/tasks/database_tasks.rb +109 -6
  120. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  121. data/lib/active_record/test_databases.rb +1 -16
  122. data/lib/active_record/test_fixtures.rb +2 -2
  123. data/lib/active_record/timestamp.rb +35 -19
  124. data/lib/active_record/touch_later.rb +4 -2
  125. data/lib/active_record/transactions.rb +55 -45
  126. data/lib/active_record/type_caster/connection.rb +16 -10
  127. data/lib/active_record/validations/uniqueness.rb +4 -4
  128. data/lib/active_record/validations.rb +1 -0
  129. data/lib/active_record.rb +7 -1
  130. data/lib/arel/insert_manager.rb +3 -3
  131. data/lib/arel/nodes/and.rb +1 -1
  132. data/lib/arel/nodes/case.rb +1 -1
  133. data/lib/arel/nodes/comment.rb +29 -0
  134. data/lib/arel/nodes/select_core.rb +16 -12
  135. data/lib/arel/nodes/unary.rb +1 -0
  136. data/lib/arel/nodes/values_list.rb +2 -17
  137. data/lib/arel/nodes.rb +2 -1
  138. data/lib/arel/select_manager.rb +10 -10
  139. data/lib/arel/visitors/depth_first.rb +7 -2
  140. data/lib/arel/visitors/dot.rb +7 -2
  141. data/lib/arel/visitors/ibm_db.rb +13 -0
  142. data/lib/arel/visitors/informix.rb +6 -0
  143. data/lib/arel/visitors/mssql.rb +15 -1
  144. data/lib/arel/visitors/oracle12.rb +4 -5
  145. data/lib/arel/visitors/postgresql.rb +4 -10
  146. data/lib/arel/visitors/to_sql.rb +107 -131
  147. data/lib/arel/visitors/visitor.rb +9 -5
  148. data/lib/arel.rb +7 -0
  149. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  150. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  151. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  152. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  153. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  154. metadata +17 -13
  155. data/lib/active_record/collection_cache_key.rb +0 -53
  156. 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,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 ActiveRecord::NullRelation === relation
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!([condition.first],
65
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
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
- if value.respond_to?(:empty?) && value.empty?
196
+ quoted = value.map { |v| c.quote(v) }
197
+ if quoted.empty?
169
198
  c.quote(nil)
170
199
  else
171
- value.map { |v| c.quote(v) }.join(",")
200
+ quoted.join(",")
172
201
  end
173
202
  else
174
203
  c.quote(value)
@@ -50,7 +50,7 @@ module ActiveRecord
50
50
  instance_eval(&block)
51
51
 
52
52
  if info[:version].present?
53
- ActiveRecord::SchemaMigration.create_table
53
+ connection.schema_migration.create_table
54
54
  connection.assume_migrated_upto_version(info[:version])
55
55
  end
56
56
 
@@ -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
- tbl.print " t.#{type} #{column.name.inspect}"
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
 
@@ -19,7 +19,7 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def table_name
22
- "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
22
+ "#{table_name_prefix}#{schema_migrations_table_name}#{table_name_suffix}"
23
23
  end
24
24
 
25
25
  def table_exists?
@@ -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? ? _scoping(relation) { yield } : relation
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 = Proc.new if block_given?
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(base_rel = nil)
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
- (base_rel ||= relation).merge!(scope)
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(base_rel) do |default_scope, scope|
119
+ default_scopes.inject(relation) do |default_scope, scope|
128
120
  scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
129
- default_scope.merge!(base_rel.instance_exec(&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 = current_scope || build_default_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
- private
27
- def current_scope(skip_inherited_scope = false)
28
- ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
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
- def current_scope=(scope)
32
- ScopeRegistry.set_value_for(:current_scope, self, scope)
33
- end
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, block = Proc.new)
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)
@@ -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