activerecord 6.0.0.beta1 → 6.0.0.beta2

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -2
  3. data/lib/active_record.rb +7 -0
  4. data/lib/active_record/associations/association.rb +17 -0
  5. data/lib/active_record/associations/collection_association.rb +5 -6
  6. data/lib/active_record/associations/collection_proxy.rb +12 -41
  7. data/lib/active_record/associations/has_many_association.rb +1 -9
  8. data/lib/active_record/associations/join_dependency/join_association.rb +11 -6
  9. data/lib/active_record/associations/preloader/association.rb +3 -4
  10. data/lib/active_record/associations/preloader/through_association.rb +9 -20
  11. data/lib/active_record/callbacks.rb +3 -3
  12. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +25 -12
  13. data/lib/active_record/connection_adapters/abstract/database_statements.rb +17 -9
  14. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -1
  15. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  16. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +47 -33
  17. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +16 -8
  18. data/lib/active_record/connection_adapters/abstract/transaction.rb +5 -2
  19. data/lib/active_record/connection_adapters/abstract_adapter.rb +6 -4
  20. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +28 -65
  21. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +1 -1
  22. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  23. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  24. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +59 -1
  25. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  26. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  27. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  28. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  29. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  30. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +15 -27
  31. data/lib/active_record/connection_adapters/postgresql_adapter.rb +30 -0
  32. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +27 -1
  33. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +8 -5
  34. data/lib/active_record/connection_handling.rb +9 -4
  35. data/lib/active_record/core.rb +13 -1
  36. data/lib/active_record/database_configurations.rb +30 -10
  37. data/lib/active_record/database_configurations/hash_config.rb +1 -1
  38. data/lib/active_record/database_configurations/url_config.rb +9 -4
  39. data/lib/active_record/errors.rb +17 -12
  40. data/lib/active_record/gem_version.rb +1 -1
  41. data/lib/active_record/inheritance.rb +1 -1
  42. data/lib/active_record/middleware/database_selector.rb +75 -0
  43. data/lib/active_record/middleware/database_selector/resolver.rb +90 -0
  44. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  45. data/lib/active_record/migration.rb +1 -1
  46. data/lib/active_record/migration/compatibility.rb +62 -63
  47. data/lib/active_record/persistence.rb +6 -6
  48. data/lib/active_record/querying.rb +2 -3
  49. data/lib/active_record/railtie.rb +9 -0
  50. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  51. data/lib/active_record/reflection.rb +15 -29
  52. data/lib/active_record/relation.rb +86 -15
  53. data/lib/active_record/relation/calculations.rb +2 -4
  54. data/lib/active_record/relation/delegation.rb +1 -1
  55. data/lib/active_record/relation/finder_methods.rb +8 -4
  56. data/lib/active_record/relation/query_attribute.rb +5 -3
  57. data/lib/active_record/relation/query_methods.rb +28 -8
  58. data/lib/active_record/relation/spawn_methods.rb +1 -1
  59. data/lib/active_record/relation/where_clause.rb +1 -5
  60. data/lib/active_record/scoping.rb +6 -7
  61. data/lib/active_record/scoping/default.rb +1 -8
  62. data/lib/active_record/scoping/named.rb +9 -1
  63. data/lib/active_record/test_fixtures.rb +2 -2
  64. data/lib/active_record/timestamp.rb +9 -3
  65. data/lib/active_record/validations/uniqueness.rb +3 -1
  66. data/lib/arel.rb +7 -0
  67. data/lib/arel/nodes/and.rb +1 -1
  68. data/lib/arel/nodes/case.rb +1 -1
  69. metadata +11 -8
@@ -1323,7 +1323,7 @@ module ActiveRecord
1323
1323
  def record_version_state_after_migrating(version)
1324
1324
  if down?
1325
1325
  migrated.delete(version)
1326
- ActiveRecord::SchemaMigration.where(version: version.to_s).delete_all
1326
+ ActiveRecord::SchemaMigration.delete_by(version: version.to_s)
1327
1327
  else
1328
1328
  migrated << version
1329
1329
  ActiveRecord::SchemaMigration.create!(version: version.to_s)
@@ -16,13 +16,55 @@ module ActiveRecord
16
16
  V6_0 = Current
17
17
 
18
18
  class V5_2 < V6_0
19
+ module TableDefinition
20
+ def timestamps(**options)
21
+ options[:precision] ||= nil
22
+ super
23
+ end
24
+ end
25
+
19
26
  module CommandRecorder
20
27
  def invert_transaction(args, &block)
21
28
  [:transaction, args, block]
22
29
  end
23
30
  end
24
31
 
32
+ def create_table(table_name, **options)
33
+ if block_given?
34
+ super { |t| yield compatible_table_definition(t) }
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def change_table(table_name, **options)
41
+ if block_given?
42
+ super { |t| yield compatible_table_definition(t) }
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ def create_join_table(table_1, table_2, **options)
49
+ if block_given?
50
+ super { |t| yield compatible_table_definition(t) }
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def add_timestamps(table_name, **options)
57
+ options[:precision] ||= nil
58
+ super
59
+ end
60
+
25
61
  private
62
+ def compatible_table_definition(t)
63
+ class << t
64
+ prepend TableDefinition
65
+ end
66
+ t
67
+ end
26
68
 
27
69
  def command_recorder
28
70
  recorder = super
@@ -35,20 +77,18 @@ module ActiveRecord
35
77
 
36
78
  class V5_1 < V5_2
37
79
  def change_column(table_name, column_name, type, options = {})
38
- if adapter_name == "PostgreSQL"
39
- clear_cache!
40
- sql = connection.send(:change_column_sql, table_name, column_name, type, options)
41
- execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}"
42
- change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
43
- change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
44
- change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
80
+ if connection.adapter_name == "PostgreSQL"
81
+ super(table_name, column_name, type, options.except(:default, :null, :comment))
82
+ connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default)
83
+ connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
84
+ connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
45
85
  else
46
86
  super
47
87
  end
48
88
  end
49
89
 
50
90
  def create_table(table_name, options = {})
51
- if adapter_name == "Mysql2"
91
+ if connection.adapter_name == "Mysql2"
52
92
  super(table_name, options: "ENGINE=InnoDB", **options)
53
93
  else
54
94
  super
@@ -70,13 +110,13 @@ module ActiveRecord
70
110
  end
71
111
 
72
112
  def create_table(table_name, options = {})
73
- if adapter_name == "PostgreSQL"
113
+ if connection.adapter_name == "PostgreSQL"
74
114
  if options[:id] == :uuid && !options.key?(:default)
75
115
  options[:default] = "uuid_generate_v4()"
76
116
  end
77
117
  end
78
118
 
79
- unless adapter_name == "Mysql2" && options[:id] == :bigint
119
+ unless connection.adapter_name == "Mysql2" && options[:id] == :bigint
80
120
  if [:integer, :bigint].include?(options[:id]) && !options.key?(:default)
81
121
  options[:default] = nil
82
122
  end
@@ -89,35 +129,12 @@ module ActiveRecord
89
129
  options[:id] = :integer
90
130
  end
91
131
 
92
- if block_given?
93
- super do |t|
94
- yield compatible_table_definition(t)
95
- end
96
- else
97
- super
98
- end
99
- end
100
-
101
- def change_table(table_name, options = {})
102
- if block_given?
103
- super do |t|
104
- yield compatible_table_definition(t)
105
- end
106
- else
107
- super
108
- end
132
+ super
109
133
  end
110
134
 
111
135
  def create_join_table(table_1, table_2, column_options: {}, **options)
112
136
  column_options.reverse_merge!(type: :integer)
113
-
114
- if block_given?
115
- super do |t|
116
- yield compatible_table_definition(t)
117
- end
118
- else
119
- super
120
- end
137
+ super
121
138
  end
122
139
 
123
140
  def add_column(table_name, column_name, type, options = {})
@@ -138,7 +155,7 @@ module ActiveRecord
138
155
  class << t
139
156
  prepend TableDefinition
140
157
  end
141
- t
158
+ super
142
159
  end
143
160
  end
144
161
 
@@ -156,33 +173,13 @@ module ActiveRecord
156
173
  end
157
174
  end
158
175
 
159
- def create_table(table_name, options = {})
160
- if block_given?
161
- super do |t|
162
- yield compatible_table_definition(t)
163
- end
164
- else
165
- super
166
- end
167
- end
168
-
169
- def change_table(table_name, options = {})
170
- if block_given?
171
- super do |t|
172
- yield compatible_table_definition(t)
173
- end
174
- else
175
- super
176
- end
177
- end
178
-
179
- def add_reference(*, **options)
176
+ def add_reference(table_name, ref_name, **options)
180
177
  options[:index] ||= false
181
178
  super
182
179
  end
183
180
  alias :add_belongs_to :add_reference
184
181
 
185
- def add_timestamps(_, **options)
182
+ def add_timestamps(table_name, **options)
186
183
  options[:null] = true if options[:null].nil?
187
184
  super
188
185
  end
@@ -193,7 +190,7 @@ module ActiveRecord
193
190
  if options[:name].present?
194
191
  options[:name].to_s
195
192
  else
196
- index_name(table_name, column: column_names)
193
+ connection.index_name(table_name, column: column_names)
197
194
  end
198
195
  super
199
196
  end
@@ -213,15 +210,17 @@ module ActiveRecord
213
210
  end
214
211
 
215
212
  def index_name_for_remove(table_name, options = {})
216
- index_name = index_name(table_name, options)
213
+ index_name = connection.index_name(table_name, options)
217
214
 
218
- unless index_name_exists?(table_name, index_name)
215
+ unless connection.index_name_exists?(table_name, index_name)
219
216
  if options.is_a?(Hash) && options.has_key?(:name)
220
217
  options_without_column = options.dup
221
218
  options_without_column.delete :column
222
- index_name_without_column = index_name(table_name, options_without_column)
219
+ index_name_without_column = connection.index_name(table_name, options_without_column)
223
220
 
224
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column)
221
+ if connection.index_name_exists?(table_name, index_name_without_column)
222
+ return index_name_without_column
223
+ end
225
224
  end
226
225
 
227
226
  raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
@@ -142,7 +142,7 @@ module ActiveRecord
142
142
  end
143
143
  end
144
144
 
145
- # Deletes the row with a primary key matching the +id+ argument, using a
145
+ # Deletes the row with a primary key matching the +id+ argument, using an
146
146
  # SQL +DELETE+ statement, and returns the number of rows deleted. Active
147
147
  # Record objects are not instantiated, so the object's callbacks are not
148
148
  # executed, including any <tt>:dependent</tt> association options.
@@ -161,7 +161,7 @@ module ActiveRecord
161
161
  # # Delete multiple rows
162
162
  # Todo.delete([2,3,4])
163
163
  def delete(id_or_array)
164
- where(primary_key => id_or_array).delete_all
164
+ delete_by(primary_key => id_or_array)
165
165
  end
166
166
 
167
167
  def _insert_record(values) # :nodoc:
@@ -436,7 +436,7 @@ module ActiveRecord
436
436
  end
437
437
 
438
438
  alias update_attributes update
439
- deprecate :update_attributes
439
+ deprecate update_attributes: "please, use update instead"
440
440
 
441
441
  # Updates its receiver just like #update but calls #save! instead
442
442
  # of +save+, so an exception is raised if the record is invalid and saving will fail.
@@ -450,7 +450,7 @@ module ActiveRecord
450
450
  end
451
451
 
452
452
  alias update_attributes! update!
453
- deprecate :update_attributes!
453
+ deprecate update_attributes!: "please, use update! instead"
454
454
 
455
455
  # Equivalent to <code>update_columns(name => value)</code>.
456
456
  def update_column(name, value)
@@ -707,10 +707,10 @@ module ActiveRecord
707
707
  )
708
708
  end
709
709
 
710
- def create_or_update(*args, &block)
710
+ def create_or_update(**, &block)
711
711
  _raise_readonly_record_error if readonly?
712
712
  return false if destroyed?
713
- result = new_record? ? _create_record(&block) : _update_record(*args, &block)
713
+ result = new_record? ? _create_record(&block) : _update_record(&block)
714
714
  result != false
715
715
  end
716
716
 
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
8
8
  delegate :find_or_create_by, :find_or_create_by!, :create_or_find_by, :create_or_find_by!, :find_or_initialize_by, to: :all
9
9
  delegate :find_by, :find_by!, to: :all
10
- delegate :destroy_all, :delete_all, :update_all, to: :all
10
+ delegate :destroy_all, :delete_all, :update_all, :destroy_by, :delete_by, to: :all
11
11
  delegate :find_each, :find_in_batches, :in_batches, to: :all
12
12
  delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or,
13
13
  :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending,
@@ -40,8 +40,7 @@ module ActiveRecord
40
40
  def find_by_sql(sql, binds = [], preparable: nil, &block)
41
41
  result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
42
42
  column_types = result_set.column_types.dup
43
- cached_columns_hash = connection.schema_cache.columns_hash(table_name)
44
- cached_columns_hash.each_key { |k| column_types.delete k }
43
+ attribute_types.each_key { |k| column_types.delete k }
45
44
  message_bus = ActiveSupport::Notifications.instrumenter
46
45
 
47
46
  payload = {
@@ -88,6 +88,14 @@ module ActiveRecord
88
88
  end
89
89
  end
90
90
 
91
+ initializer "active_record.database_selector" do
92
+ if options = config.active_record.delete(:database_selector)
93
+ resolver = config.active_record.delete(:database_resolver)
94
+ operations = config.active_record.delete(:database_resolver_context)
95
+ config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
96
+ end
97
+ end
98
+
91
99
  initializer "Check for cache versioning support" do
92
100
  config.after_initialize do |app|
93
101
  ActiveSupport.on_load(:active_record) do
@@ -189,6 +197,7 @@ end_error
189
197
  # and then establishes the connection.
190
198
  initializer "active_record.initialize_database" do
191
199
  ActiveSupport.on_load(:active_record) do
200
+ self.connection_handlers = { writing_role => ActiveRecord::Base.default_connection_handler }
192
201
  self.configurations = Rails.application.config.database_configuration
193
202
  establish_connection
194
203
  end
@@ -3,7 +3,7 @@
3
3
  module ActiveRecord
4
4
  module Railties # :nodoc:
5
5
  module CollectionCacheAssociationLoading #:nodoc:
6
- def setup(context, options, block)
6
+ def setup(context, options, as, block)
7
7
  @relation = relation_from_options(options)
8
8
 
9
9
  super
@@ -20,12 +20,12 @@ module ActiveRecord
20
20
  end
21
21
  end
22
22
 
23
- def collection_without_template
23
+ def collection_without_template(*)
24
24
  @relation.preload_associations(@collection) if @relation
25
25
  super
26
26
  end
27
27
 
28
- def collection_with_template
28
+ def collection_with_template(*)
29
29
  @relation.preload_associations(@collection) if @relation
30
30
  super
31
31
  end
@@ -178,28 +178,24 @@ module ActiveRecord
178
178
  scope ? [scope] : []
179
179
  end
180
180
 
181
- def build_join_constraint(table, foreign_table)
182
- key = join_keys.key
183
- foreign_key = join_keys.foreign_key
184
-
185
- constraint = table[key].eq(foreign_table[foreign_key])
186
-
187
- if klass.finder_needs_type_condition?
188
- table.create_and([constraint, klass.send(:type_condition, table)])
189
- else
190
- constraint
191
- end
192
- end
193
-
194
- def join_scope(table, foreign_klass)
181
+ def join_scope(table, foreign_table, foreign_klass)
195
182
  predicate_builder = predicate_builder(table)
196
183
  scope_chain_items = join_scopes(table, predicate_builder)
197
184
  klass_scope = klass_join_scope(table, predicate_builder)
198
185
 
186
+ key = join_keys.key
187
+ foreign_key = join_keys.foreign_key
188
+
189
+ klass_scope.where!(table[key].eq(foreign_table[foreign_key]))
190
+
199
191
  if type
200
192
  klass_scope.where!(type => foreign_klass.polymorphic_name)
201
193
  end
202
194
 
195
+ if klass.finder_needs_type_condition?
196
+ klass_scope.where!(klass.send(:type_condition, table))
197
+ end
198
+
203
199
  scope_chain_items.inject(klass_scope, &:merge!)
204
200
  end
205
201
 
@@ -612,21 +608,9 @@ module ActiveRecord
612
608
 
613
609
  # returns either +nil+ or the inverse association name that it finds.
614
610
  def automatic_inverse_of
615
- return unless can_find_inverse_of_automatically?(self)
611
+ if can_find_inverse_of_automatically?(self)
612
+ inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
616
613
 
617
- inverse_name_candidates =
618
- if options[:as]
619
- [options[:as]]
620
- else
621
- active_record_name = active_record.name.demodulize
622
- [active_record_name, ActiveSupport::Inflector.pluralize(active_record_name)]
623
- end
624
-
625
- inverse_name_candidates.map! do |candidate|
626
- ActiveSupport::Inflector.underscore(candidate).to_sym
627
- end
628
-
629
- inverse_name_candidates.detect do |inverse_name|
630
614
  begin
631
615
  reflection = klass._reflect_on_association(inverse_name)
632
616
  rescue NameError
@@ -635,7 +619,9 @@ module ActiveRecord
635
619
  reflection = false
636
620
  end
637
621
 
638
- valid_inverse_reflection?(reflection)
622
+ if valid_inverse_reflection?(reflection)
623
+ return inverse_name
624
+ end
639
625
  end
640
626
  end
641
627
 
@@ -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:
@@ -312,12 +323,12 @@ module ActiveRecord
312
323
  # Please check unscoped if you want to remove all previous scopes (including
313
324
  # the default_scope) during the execution of a block.
314
325
  def scoping
315
- @delegate_to_klass ? yield : klass._scoping(self) { yield }
326
+ already_in_scope? ? yield : _scoping(self) { yield }
316
327
  end
317
328
 
318
- def _exec_scope(*args, &block) # :nodoc:
329
+ def _exec_scope(name, *args, &block) # :nodoc:
319
330
  @delegate_to_klass = true
320
- instance_exec(*args, &block) || self
331
+ _scoping(_deprecated_spawn(name)) { instance_exec(*args, &block) || self }
321
332
  ensure
322
333
  @delegate_to_klass = false
323
334
  end
@@ -361,6 +372,12 @@ module ActiveRecord
361
372
  stmt.wheres = arel.constraints
362
373
 
363
374
  if updates.is_a?(Hash)
375
+ if klass.locking_enabled? &&
376
+ !updates.key?(klass.locking_column) &&
377
+ !updates.key?(klass.locking_column.to_sym)
378
+ attr = arel_attribute(klass.locking_column)
379
+ updates[attr.name] = _increment_attribute(attr)
380
+ end
364
381
  stmt.set _substitute_values(updates)
365
382
  else
366
383
  stmt.set Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
@@ -383,10 +400,7 @@ module ActiveRecord
383
400
  updates = {}
384
401
  counters.each do |counter_name, value|
385
402
  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
403
+ updates[attr.name] = _increment_attribute(attr, value)
390
404
  end
391
405
 
392
406
  if touch
@@ -422,12 +436,7 @@ module ActiveRecord
422
436
  # Person.where(name: 'David').touch_all
423
437
  # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
424
438
  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
439
+ update_all klass.touch_attributes_with_time(*names, time: time)
431
440
  end
432
441
 
433
442
  # Destroys the records by instantiating each
@@ -496,6 +505,32 @@ module ActiveRecord
496
505
  affected
497
506
  end
498
507
 
508
+ # Finds and destroys all records matching the specified conditions.
509
+ # This is short-hand for <tt>relation.where(condition).destroy_all</tt>.
510
+ # Returns the collection of objects that were destroyed.
511
+ #
512
+ # If no record is found, returns empty array.
513
+ #
514
+ # Person.destroy_by(id: 13)
515
+ # Person.destroy_by(name: 'Spartacus', rating: 4)
516
+ # Person.destroy_by("published_at < ?", 2.weeks.ago)
517
+ def destroy_by(*args)
518
+ where(*args).destroy_all
519
+ end
520
+
521
+ # Finds and deletes all records matching the specified conditions.
522
+ # This is short-hand for <tt>relation.where(condition).delete_all</tt>.
523
+ # Returns the number of rows affected.
524
+ #
525
+ # If no record is found, returns <tt>0</tt> as zero rows were affected.
526
+ #
527
+ # Person.delete_by(id: 13)
528
+ # Person.delete_by(name: 'Spartacus', rating: 4)
529
+ # Person.delete_by("published_at < ?", 2.weeks.ago)
530
+ def delete_by(*args)
531
+ where(*args).delete_all
532
+ end
533
+
499
534
  # Causes the records to be loaded from the database if they have not
500
535
  # been loaded already. You can use this if for some reason you need
501
536
  # to explicitly load some records before actually using them. The
@@ -516,6 +551,7 @@ module ActiveRecord
516
551
 
517
552
  def reset
518
553
  @delegate_to_klass = false
554
+ @_deprecated_scope_source = nil
519
555
  @to_sql = @arel = @loaded = @should_eager_load = nil
520
556
  @records = [].freeze
521
557
  @offsets = {}
@@ -624,7 +660,10 @@ module ActiveRecord
624
660
  end
625
661
  end
626
662
 
663
+ attr_reader :_deprecated_scope_source # :nodoc:
664
+
627
665
  protected
666
+ attr_writer :_deprecated_scope_source # :nodoc:
628
667
 
629
668
  def load_records(records)
630
669
  @records = records.freeze
@@ -632,6 +671,31 @@ module ActiveRecord
632
671
  end
633
672
 
634
673
  private
674
+ def already_in_scope?
675
+ @delegate_to_klass && begin
676
+ scope = klass.current_scope(true)
677
+ scope && !scope._deprecated_scope_source
678
+ end
679
+ end
680
+
681
+ def _deprecated_spawn(name)
682
+ spawn.tap { |scope| scope._deprecated_scope_source = name }
683
+ end
684
+
685
+ def _deprecated_scope_block(name, &block)
686
+ -> record do
687
+ klass.current_scope = _deprecated_spawn(name)
688
+ yield record if block_given?
689
+ end
690
+ end
691
+
692
+ def _scoping(scope)
693
+ previous, klass.current_scope = klass.current_scope(true), scope
694
+ yield
695
+ ensure
696
+ klass.current_scope = previous
697
+ end
698
+
635
699
  def _substitute_values(values)
636
700
  values.map do |name, value|
637
701
  attr = arel_attribute(name)
@@ -643,6 +707,13 @@ module ActiveRecord
643
707
  end
644
708
  end
645
709
 
710
+ def _increment_attribute(attribute, value = 1)
711
+ bind = predicate_builder.build_bind_attribute(attribute.name, value.abs)
712
+ expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attribute), 0)
713
+ expr = value < 0 ? expr - bind : expr + bind
714
+ expr.expr
715
+ end
716
+
646
717
  def exec_queries(&block)
647
718
  skip_query_cache_if_necessary do
648
719
  @records =