activerecord-updateinbulk 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a844813b98d092d304542eb4f8c3ffc6b99a50f7510d430bef229e47c969415
4
- data.tar.gz: e83aa71485ad9255b977b96afe9a8ec55820eda485d489d16e30629c1a79a971
3
+ metadata.gz: c5130d637025d39db5e0eab8ecb59b22252decd67a56a089c7e1a749ac70385f
4
+ data.tar.gz: 44dbf800efb3d5b0e4d405864cf1ed0e2c3e87d461c10a72cf03513aa6878c5e
5
5
  SHA512:
6
- metadata.gz: 45d04870ea3981cc5bb3533572f8fd412cc442bfbde77163d2d78bb47af3e3234658db990afdea09fc754f6d492ed400bc50f7f23508f01a82614f1803fae05e
7
- data.tar.gz: 33dfb33a7963b357eb8da64afc3353e671fa33cab926e8bf3b9380ccecd47dbae1360f776159e35dc02a44c0c8f98721d7ed58306eebc456bf89673b0fc14900
6
+ metadata.gz: 02f007dd0c1c2a9f46f48a414bbe352598c0b7a36793f71070e63e7160c4decc01db790c727f90882a40fc302a7b486608548908b8c4ad996d1b858ff998220f
7
+ data.tar.gz: a54913d72fe2bed929e7676e2cf09d39bd589519529eb7576f1833108f77984072645829b453333968a2d4eca90f455493260970ee8e937929e0a00ed46fdc4f
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --quiet
2
+ lib/activerecord-updateinbulk/relation.rb
3
+ lib/activerecord-updateinbulk/adapters/abstract_adapter.rb
4
+ lib/activerecord-updateinbulk/arel/nodes/values_table.rb
5
+ lib/activerecord-updateinbulk/railtie.rb
6
+ lib/activerecord-updateinbulk/version.rb
7
+ -
8
+ README.md
data/README.md CHANGED
@@ -61,17 +61,10 @@ Order.joins(:items).where(items: { status: :shipped }).update_in_bulk({
61
61
 
62
62
  ### Rails configuration
63
63
 
64
- Railtie options are available at `config.active_record_update_in_bulk`:
64
+ Railtie initializer options are available at `config.active_record_update_in_bulk`:
65
65
 
66
66
  - `values_table_alias` (`String`, optional): alias used for generated VALUES tables (default `"t"`).
67
- - `ignore_scope_order` (`Boolean`, default `true`): when true, ORDER BY scopes are ignored by `update_in_bulk`; when false, ordered relations raise `NotImplementedError`.
68
-
69
- Example initializer:
70
-
71
- ```ruby
72
- Rails.application.config.active_record_update_in_bulk.values_table_alias = "vals"
73
- Rails.application.config.active_record_update_in_bulk.ignore_scope_order = true
74
- ```
67
+ - `ignore_scope_order` (`Boolean`, default `true`): when true, ORDER BY scopes are ignored by `update_in_bulk`.
75
68
 
76
69
  ### Record timestamps
77
70
 
@@ -21,7 +21,7 @@ module ActiveRecord::UpdateInBulk
21
21
  end
22
22
 
23
23
  # Whether VALUES table serialization must always include explicit column
24
- # aliases (because defaults are missing or not statically known).
24
+ # aliases because there are no default names. This is a mariadb quirk.
25
25
  def values_table_requires_aliasing?
26
26
  false
27
27
  end
@@ -44,10 +44,15 @@ module ActiveRecord::UpdateInBulk
44
44
  # possibly modified in place.
45
45
  #
46
46
  # The default implementation does no explicit type casting.
47
- def typecast_values_table(values_table, _columns)
47
+ def typecast_values_table(values_table, columns)
48
48
  values_table
49
49
  end
50
50
  end
51
51
  end
52
52
 
53
53
  ActiveRecord::ConnectionAdapters::AbstractAdapter.include(ActiveRecord::UpdateInBulk::AbstractAdapter)
54
+
55
+ # @!parse
56
+ # class ActiveRecord::ConnectionAdapters::AbstractAdapter
57
+ # include ActiveRecord::UpdateInBulk::AbstractAdapter
58
+ # end
@@ -3,7 +3,7 @@
3
3
  require "active_record/connection_adapters/abstract_mysql_adapter"
4
4
 
5
5
  module ActiveRecord::UpdateInBulk
6
- module AbstractMysqlAdapter
6
+ module AbstractMysqlAdapter # :nodoc: all
7
7
  def supports_values_tables?
8
8
  mariadb? ? database_version >= "10.3.3" : database_version >= "8.0.19"
9
9
  end
@@ -3,7 +3,7 @@
3
3
  require "active_record/connection_adapters/postgresql_adapter"
4
4
 
5
5
  module ActiveRecord::UpdateInBulk
6
- module PostgreSQLAdapter
6
+ module PostgreSQLAdapter # :nodoc: all
7
7
  SAFE_TYPES_FOR_VALUES_TABLE = [:integer, :string, :text, :boolean].freeze
8
8
 
9
9
 
@@ -3,7 +3,7 @@
3
3
  require "active_record/connection_adapters/sqlite3_adapter"
4
4
 
5
5
  module ActiveRecord::UpdateInBulk
6
- module SQLite3Adapter
6
+ module SQLite3Adapter # :nodoc: all
7
7
  end
8
8
  end
9
9
 
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Arel::Nodes
4
- # Represents a SQL VALUES table constructor as an Arel node.
5
- # Mirrors Arel::Table behavior by requiring a name at construction time.
6
- # Column names are also required because adapter defaults vary; obtain
7
- # defaults through <tt>connection.values_table_default_column_names(width)</tt>.
4
+ # Represents the +VALUES+ table constructor as an Arel node.
5
+ # Mirrors +Arel::Table+ behavior by requiring a name at construction time.
6
+ # Column names are also required because adapter defaults vary; prefer using the
7
+ # default names from <tt>connection.values_table_default_column_names(width)</tt>
8
+ # to keep the generated query simple.
9
+ #
10
+ # This is a private class that may be used by typecasting logic in custom adapters.
8
11
  #
9
12
  class ValuesTable < Arel::Nodes::Node
10
13
  attr_reader :name, :width, :rows, :columns
@@ -35,7 +38,9 @@ module Arel::Nodes
35
38
  Arel::Nodes::TableAlias.new(grouping(self), table)
36
39
  end
37
40
 
38
- delegate :to_cte, to: :alias
41
+ def to_cte
42
+ self.alias.to_cte
43
+ end
39
44
 
40
45
  def hash
41
46
  [@name, @rows, @columns].hash
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::UpdateInBulk
4
- module SelectManager # :nodoc:
4
+ module SelectManager # :nodoc: all
5
5
  def alias(name) # :nodoc:
6
6
  Arel::Nodes::TableAlias.new(self, name)
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::UpdateInBulk
4
- module ToSql
4
+ module ToSql # :nodoc: all
5
5
  def visit_Arel_Nodes_ValuesTable(o, collector)
6
6
  row_prefix = @connection.values_table_row_prefix
7
7
 
@@ -19,15 +19,15 @@ module ActiveRecord
19
19
  require_adapter connection_pool.db_config.adapter
20
20
  end
21
21
 
22
- def self.register_formula(name, &formula)
22
+ def self.register_formula(name, &formula) # :nodoc:
23
23
  Builder.register_formula(name, &formula)
24
24
  end
25
25
 
26
- def self.unregister_formula(name)
26
+ def self.unregister_formula(name) # :nodoc:
27
27
  Builder.unregister_formula(name)
28
28
  end
29
29
 
30
- def self.registered_formula?(name)
30
+ def self.registered_formula?(name) # :nodoc:
31
31
  Builder.registered_formula?(name)
32
32
  end
33
33
  end
@@ -43,7 +43,7 @@ require "activerecord-updateinbulk/querying"
43
43
  require "activerecord-updateinbulk/adapters/abstract_adapter"
44
44
 
45
45
  module ActiveRecord::UpdateInBulk
46
- module ConnectionHandler # :nodoc:
46
+ module ConnectionHandler # :nodoc: all
47
47
  def establish_connection(*args, **kwargs, &block) # :nodoc:
48
48
  pool = super(*args, **kwargs, &block)
49
49
  ActiveRecord::UpdateInBulk.load_from_connection_pool pool
@@ -3,7 +3,7 @@
3
3
  require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActiveRecord::UpdateInBulk
6
- class Builder # :nodoc:
6
+ class Builder # :nodoc: all
7
7
  SAFE_COMPARISON_TYPES = [:boolean, :string, :text, :integer, :float, :decimal].freeze
8
8
 
9
9
  class << self
@@ -138,9 +138,11 @@ module ActiveRecord::UpdateInBulk
138
138
  @conditions = conditions
139
139
  @assigns = assigns
140
140
  @formulas = normalize_formulas(formulas)
141
+ @auto_locking_column = nil
141
142
 
142
143
  resolve_attribute_aliases!
143
144
  resolve_read_and_write_keys!
145
+ apply_optimistic_locking!
144
146
  verify_read_and_write_keys!
145
147
  serialize_values!
146
148
  detect_constant_columns! unless simple_update?
@@ -222,7 +224,7 @@ module ActiveRecord::UpdateInBulk
222
224
 
223
225
  def build_values_table_rows
224
226
  bitmask_keys = Set.new
225
- non_constant_write_keys = write_keys - constant_assigns.keys
227
+ non_constant_write_keys = write_keys.reject { |key| constant_assigns.key?(key) }
226
228
 
227
229
  rows = @conditions.map.with_index do |row_conditions, row_index|
228
230
  row_assigns = @assigns[row_index]
@@ -264,6 +266,10 @@ module ActiveRecord::UpdateInBulk
264
266
  else
265
267
  build_simple_assignments(table)
266
268
  end
269
+ if @auto_locking_column
270
+ lock = table[@auto_locking_column]
271
+ set_assignments << [lock, table.coalesce(lock, 0) + 1]
272
+ end
267
273
 
268
274
  if timestamp_keys.any?
269
275
  # Timestamp assignments precede data assignments to increase the
@@ -288,7 +294,8 @@ module ActiveRecord::UpdateInBulk
288
294
  if constant_assigns.key?(key)
289
295
  rhs = Arel::Nodes::Quoted.new(constant_assigns[key])
290
296
  else
291
- rhs = values_table[column]
297
+ val = values_table[column]
298
+ rhs = val
292
299
  column += 1
293
300
  rhs = self.class.apply_formula(formula, lhs, rhs, model) if formula
294
301
  end
@@ -296,7 +303,11 @@ module ActiveRecord::UpdateInBulk
296
303
  if function = bitmask_functions[key]
297
304
  rhs = Arel::Nodes::Case.new(function).when("1").then(rhs).else(lhs)
298
305
  elsif optional_keys.include?(key)
299
- rhs = table.coalesce(rhs, lhs)
306
+ if formula
307
+ rhs = Arel::Nodes::Case.new.when(val.eq(nil)).then(lhs).else(rhs)
308
+ else
309
+ rhs = table.coalesce(rhs, lhs)
310
+ end
300
311
  end
301
312
  [lhs, rhs]
302
313
  end
@@ -359,6 +370,15 @@ module ActiveRecord::UpdateInBulk
359
370
  normalized
360
371
  end
361
372
 
373
+ def apply_optimistic_locking!
374
+ return unless model.locking_enabled?
375
+
376
+ locking_column = model.locking_column
377
+ return if write_keys.include?(locking_column)
378
+
379
+ @auto_locking_column = locking_column
380
+ end
381
+
362
382
  def resolve_attribute_aliases!
363
383
  return if model.attribute_aliases.empty?
364
384
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord::UpdateInBulk
4
- module Querying # :nodoc:
4
+ module Querying # :nodoc: all
5
5
  def update_in_bulk(...) # :nodoc:
6
6
  all.update_in_bulk(...)
7
7
  end
@@ -15,16 +15,15 @@ module ActiveRecord
15
15
  # Rails.application.config.active_record_update_in_bulk.ignore_scope_order = true
16
16
  #
17
17
  # [config.active_record_update_in_bulk.values_table_alias]
18
- # Optional string alias to use for the generated VALUES table.
19
- # Defaults to <tt>"t"</tt>.
18
+ # Table alias for the generated +VALUES+ table. Defaults to <tt>"t"</tt>.
20
19
  #
21
20
  # [config.active_record_update_in_bulk.ignore_scope_order]
22
- # Whether <tt>Relation#update_in_bulk</tt> should ignore any ORDER BY scope
23
- # on the input relation. Necessary for invoking the method on scope-ordered
24
- # associations, or models with a default scope that includes an order.
21
+ # Whether <tt>Relation#update_in_bulk</tt> should ignore any +ORDER BY+ scope
22
+ # on the input relation. Necessary for invoking the method casually on scope
23
+ # ordered associations, or models with a default scope that includes an order.
25
24
  #
26
- # * <tt>true</tt> (default): ORDER BY scopes are stripped.
27
- # * <tt>false</tt>: ordered relations raise NotImplementedError.
25
+ # * <tt>true</tt> (default): +ORDER BY+ clause is stripped.
26
+ # * <tt>false</tt>: ordered relations raise +NotImplementedError+.
28
27
  class Railtie < Rails::Railtie
29
28
  config.active_record_update_in_bulk = ActiveSupport::OrderedOptions.new
30
29
  config.active_record_update_in_bulk.ignore_scope_order = true
@@ -3,14 +3,14 @@
3
3
  module ActiveRecord::UpdateInBulk
4
4
  module Relation
5
5
  # Updates multiple groups of records in the current relation using a single
6
- # SQL UPDATE statement. This does not instantiate models and does not
6
+ # SQL +UPDATE+ statement. This does not instantiate models and does not
7
7
  # trigger Active Record callbacks or validations. However, values passed
8
8
  # through still use Active Record's normal type casting and serialization.
9
9
  # Returns the number of rows affected.
10
10
  #
11
- # Three equivalent input formats are supported:
11
+ # Three equivalent input formats are supported for convenience:
12
12
  #
13
- # *Indexed format* — a hash mapping primary keys to attribute updates:
13
+ # <b>Indexed format</b> — a hash mapping primary keys to attribute updates:
14
14
  #
15
15
  # Book.update_in_bulk({
16
16
  # 1 => { title: "Agile", price: 10.0 },
@@ -24,7 +24,7 @@ module ActiveRecord::UpdateInBulk
24
24
  # ["AA100", "12B"] => { passenger: "Bob" }
25
25
  # })
26
26
  #
27
- # *Paired format* — an array of <tt>[conditions, assigns]</tt> pairs.
27
+ # <b>Paired format</b> — an array of <tt>[conditions, assigns]</tt> pairs.
28
28
  # Conditions do not need to be primary keys; they may reference any columns in
29
29
  # the target table. All pairs must specify the same set of condition
30
30
  # columns:
@@ -34,7 +34,7 @@ module ActiveRecord::UpdateInBulk
34
34
  # [{ department: "Engineering" }, { bonus: 500 }]
35
35
  # ])
36
36
  #
37
- # *Separated format* — parallel arrays of conditions and assigns:
37
+ # <b>Separated format</b> — parallel arrays of conditions and assigns:
38
38
  #
39
39
  # Employee.update_in_bulk(
40
40
  # [1, 2, { id: 3 }],
@@ -90,6 +90,12 @@ module ActiveRecord::UpdateInBulk
90
90
  # 2 => { department: "Sales" }
91
91
  # })
92
92
  #
93
+ # ==== Restrictions
94
+ #
95
+ # This method does not support relations with <tt>offset</tt>, <tt>limit</tt>,
96
+ # <tt>group</tt>, or <tt>having</tt> clauses. An <tt>order</tt> clause is supported
97
+ # by default <b>by being stripped</b> to keep the method usable on ordered associations.
98
+ #
93
99
  def update_in_bulk(updates, values = nil, record_timestamps: nil, formulas: nil)
94
100
  unless limit_value.nil? && offset_value.nil? && group_values.empty? && having_clause.empty?
95
101
  raise NotImplementedError, "No support to update relations with offset, limit, group, or having clauses"
@@ -137,3 +143,8 @@ module ActiveRecord::UpdateInBulk
137
143
  end
138
144
 
139
145
  ActiveRecord::Relation.prepend(ActiveRecord::UpdateInBulk::Relation)
146
+
147
+ # @!parse
148
+ # class ActiveRecord::Relation
149
+ # include ActiveRecord::UpdateInBulk::Relation
150
+ # end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module UpdateInBulk
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-updateinbulk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Carvalho
@@ -31,6 +31,7 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - ".yardopts"
34
35
  - LICENSE
35
36
  - README.md
36
37
  - lib/activerecord-updateinbulk.rb