activerecord-updateinbulk 0.2.0 → 0.2.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: 25ba0ab087a02a9c275429d50daa9b31bc2bc93fe4ae40c726402f4fd2f21ec2
4
- data.tar.gz: d32540835f40aaaf11aa0180d9f49763c775ff7b55287dec5cd9db568b20f45f
3
+ metadata.gz: 1a844813b98d092d304542eb4f8c3ffc6b99a50f7510d430bef229e47c969415
4
+ data.tar.gz: e83aa71485ad9255b977b96afe9a8ec55820eda485d489d16e30629c1a79a971
5
5
  SHA512:
6
- metadata.gz: 2dee9dd00a22a4bebf1d692d926ce2dfafaf2730b3163ee384dda5038616fbef79812caba778280d7ed36523e5f0433dac5cbfc19667c8d038a79bf10fc6db0e
7
- data.tar.gz: 8273e2806d3dfceca28214e461b0a6a7954d613e2fc9dfeebc4c754e1582121473a87817fdbc14498d534429163f3554f82ba46eb6b10c1a322aedbcffc558ae
6
+ metadata.gz: 45d04870ea3981cc5bb3533572f8fd412cc442bfbde77163d2d78bb47af3e3234658db990afdea09fc754f6d492ed400bc50f7f23508f01a82614f1803fae05e
7
+ data.tar.gz: 33dfb33a7963b357eb8da64afc3353e671fa33cab926e8bf3b9380ccecd47dbae1360f776159e35dc02a44c0c8f98721d7ed58306eebc456bf89673b0fc14900
@@ -12,8 +12,9 @@ module ActiveRecord::UpdateInBulk
12
12
  case column
13
13
  when ActiveRecord::ConnectionAdapters::PostgreSQL::Column
14
14
  if SAFE_TYPES_FOR_VALUES_TABLE.exclude?(column.type) ||
15
+ column.array ||
15
16
  values_table.rows.all? { |row| row[index].nil? }
16
- column.sql_type
17
+ column.sql_type_metadata.sql_type
17
18
  end
18
19
  when Arel::Nodes::SqlLiteral, nil
19
20
  column
@@ -18,6 +18,18 @@ module ActiveRecord
18
18
  def self.load_from_connection_pool(connection_pool) # :nodoc:
19
19
  require_adapter connection_pool.db_config.adapter
20
20
  end
21
+
22
+ def self.register_formula(name, &formula)
23
+ Builder.register_formula(name, &formula)
24
+ end
25
+
26
+ def self.unregister_formula(name)
27
+ Builder.unregister_formula(name)
28
+ end
29
+
30
+ def self.registered_formula?(name)
31
+ Builder.registered_formula?(name)
32
+ end
21
33
  end
22
34
  end
23
35
 
@@ -4,13 +4,31 @@ require "active_support/core_ext/enumerable"
4
4
 
5
5
  module ActiveRecord::UpdateInBulk
6
6
  class Builder # :nodoc:
7
- FORMULAS = [:add, :subtract, :concat_append, :concat_prepend].freeze
8
7
  SAFE_COMPARISON_TYPES = [:boolean, :string, :text, :integer, :float, :decimal].freeze
9
8
 
10
9
  class << self
11
10
  attr_accessor :values_table_name
12
11
  attr_accessor :ignore_scope_order
13
12
 
13
+ def register_formula(name, &formula)
14
+ raise ArgumentError, "Missing block" unless formula
15
+
16
+ name = name.to_sym
17
+ if registered_formulas.key?(name)
18
+ raise ArgumentError, "Formula already registered: #{name.inspect}"
19
+ end
20
+
21
+ registered_formulas[name] = formula
22
+ end
23
+
24
+ def unregister_formula(name)
25
+ registered_formulas.delete(name.to_sym)
26
+ end
27
+
28
+ def registered_formula?(name)
29
+ registered_formulas.key?(name.to_sym)
30
+ end
31
+
14
32
  # Normalize all input formats into separated format [conditions, assigns].
15
33
  def normalize_updates(model, updates, values = nil)
16
34
  conditions = []
@@ -48,39 +66,37 @@ module ActiveRecord::UpdateInBulk
48
66
  end
49
67
 
50
68
  def apply_formula(formula, lhs, rhs, model)
51
- formula = formula.to_sym if formula.is_a?(String)
52
- case formula
53
- when :add
54
- lhs + rhs
55
- when :subtract
56
- lhs - rhs
57
- when :concat_append
58
- lhs.concat(rhs)
59
- when :concat_prepend
60
- rhs.concat(lhs)
61
- when Proc
62
- node = apply_proc_formula(formula, lhs, rhs, model)
63
- unless Arel.arel_node?(node)
64
- raise ArgumentError, "Custom formula must return an Arel node"
65
- end
66
- node
69
+ formula_proc = case formula
70
+ when Proc then formula
67
71
  else
68
- raise ArgumentError, "Unknown formula: #{formula.inspect}"
72
+ registered_formulas.fetch(formula.to_sym) do
73
+ raise ArgumentError, "Unknown formula: #{formula.inspect}"
74
+ end
69
75
  end
76
+
77
+ node = apply_proc_formula(formula_proc, lhs, rhs, model)
78
+ raise ArgumentError, "Custom formula must return an Arel node" unless Arel.arel_node?(node)
79
+
80
+ node
70
81
  end
71
82
 
72
83
  def apply_proc_formula(formula, lhs, rhs, model)
73
84
  case formula.arity
74
- when 2
75
- formula.call(lhs, rhs)
76
- when 3
77
- formula.call(lhs, rhs, model)
78
- else
79
- raise ArgumentError, "Custom formula must accept 2 or 3 arguments"
85
+ when 2 then formula.call(lhs, rhs)
86
+ else formula.call(lhs, rhs, model)
80
87
  end
81
88
  end
82
89
 
83
90
  private
91
+ def registered_formulas
92
+ @registered_formulas ||= {
93
+ add: lambda { |lhs, rhs| lhs + rhs },
94
+ subtract: lambda { |lhs, rhs| lhs - rhs },
95
+ concat_append: lambda { |lhs, rhs| lhs.concat(rhs) },
96
+ concat_prepend: lambda { |lhs, rhs| rhs.concat(lhs) }
97
+ }
98
+ end
99
+
84
100
  def normalize_conditions(model, conditions)
85
101
  if conditions.is_a?(Hash)
86
102
  conditions
@@ -126,10 +142,8 @@ module ActiveRecord::UpdateInBulk
126
142
  resolve_attribute_aliases!
127
143
  resolve_read_and_write_keys!
128
144
  verify_read_and_write_keys!
129
- unless simple_update?
130
- detect_constant_columns!
131
- serialize_values!
132
- end
145
+ serialize_values!
146
+ detect_constant_columns! unless simple_update?
133
147
  end
134
148
 
135
149
  def build_arel
@@ -160,26 +174,24 @@ module ActiveRecord::UpdateInBulk
160
174
  def build_simple_conditions(table)
161
175
  row_conditions = @conditions.first
162
176
  read_keys.map do |key|
163
- table[key].eq(cast_for_column(row_conditions.fetch(key), table[key]))
177
+ table[key].eq(quoted_value(row_conditions.fetch(key)))
164
178
  end
165
179
  end
166
180
 
167
181
  def build_simple_assignments(table)
168
182
  row_assigns = @assigns.first
169
183
  write_keys.map do |key|
170
- [table[key], cast_for_column(row_assigns.fetch(key), table[key])]
184
+ [table[key], quoted_value(row_assigns.fetch(key))]
171
185
  end
172
186
  end
173
187
 
174
188
  def detect_constant_columns!
175
189
  @constant_assigns = {}
176
- columns_hash = model.columns_hash
177
190
 
178
191
  (write_keys - optional_keys).each do |key|
179
192
  next if @formulas.key?(key) # need to pass Arel::Attribute as argument to formula
180
- next unless SAFE_COMPARISON_TYPES.include?(columns_hash.fetch(key).type)
181
193
  first = @assigns.first[key]
182
- @constant_assigns[key] = first if @assigns.all? { |a| !opaque_value?(v = a[key]) && v == first }
194
+ @constant_assigns[key] = first if @assigns.all? { |a| !Arel.arel_node?(v = a[key]) && v == first }
183
195
  end
184
196
  end
185
197
 
@@ -187,14 +199,14 @@ module ActiveRecord::UpdateInBulk
187
199
  types = read_keys.index_with { |key| model.type_for_attribute(key) }
188
200
  @conditions.each do |row|
189
201
  row.each do |key, value|
190
- next if opaque_value?(value)
202
+ next if Arel.arel_node?(value)
191
203
  row[key] = ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
192
204
  end
193
205
  end
194
206
  types = write_keys.index_with { |key| model.type_for_attribute(key) }
195
207
  @assigns.each do |row|
196
208
  row.each do |key, value|
197
- next if opaque_value?(value) || constant_assigns.key?(key)
209
+ next if Arel.arel_node?(value)
198
210
  row[key] = ActiveModel::Type::SerializeCastValue.serialize(type = types[key], type.cast(value))
199
211
  end
200
212
  end
@@ -274,7 +286,7 @@ module ActiveRecord::UpdateInBulk
274
286
  lhs = table[key]
275
287
 
276
288
  if constant_assigns.key?(key)
277
- rhs = Arel::Nodes::Casted.new(constant_assigns[key], lhs)
289
+ rhs = Arel::Nodes::Quoted.new(constant_assigns[key])
278
290
  else
279
291
  rhs = values_table[column]
280
292
  column += 1
@@ -327,15 +339,11 @@ module ActiveRecord::UpdateInBulk
327
339
  # When you assign a value to NULL, we need to use a bitmask to distinguish that
328
340
  # row in the values table from rows where the column is not to be assigned at all.
329
341
  def might_be_nil_value?(value)
330
- value.nil? || opaque_value?(value)
331
- end
332
-
333
- def opaque_value?(value)
334
- Arel.arel_node?(value)
342
+ value.nil? || Arel.arel_node?(value)
335
343
  end
336
344
 
337
- def cast_for_column(value, column)
338
- opaque_value?(value) ? value : Arel::Nodes::Casted.new(value, column)
345
+ def quoted_value(value)
346
+ Arel.arel_node?(value) ? value : Arel::Nodes::Quoted.new(value)
339
347
  end
340
348
 
341
349
  def normalize_formulas(formulas)
@@ -344,7 +352,7 @@ module ActiveRecord::UpdateInBulk
344
352
  normalized = formulas.to_h do |key, value|
345
353
  [key.to_s, value.is_a?(Proc) ? value : value.to_sym]
346
354
  end
347
- invalid = normalized.values.reject { |v| v.is_a?(Proc) } - FORMULAS
355
+ invalid = normalized.values.reject { |v| v.is_a?(Proc) || self.class.registered_formula?(v) }
348
356
  if invalid.any?
349
357
  raise ArgumentError, "Unknown formula: #{invalid.first.inspect}"
350
358
  end
@@ -29,21 +29,25 @@ module ActiveRecord
29
29
  config.active_record_update_in_bulk = ActiveSupport::OrderedOptions.new
30
30
  config.active_record_update_in_bulk.ignore_scope_order = true
31
31
 
32
- initializer "active_record_update_in_bulk.values_table_alias", after: :load_config_initializers do |app|
33
- if (bulk_alias = app.config.active_record_update_in_bulk.values_table_alias)
34
- unless bulk_alias.instance_of?(String) && !bulk_alias.empty?
35
- raise ArgumentError, "values_table_alias must be a non-empty String"
32
+ initializer "active_record_update_in_bulk.values_table_alias" do |app|
33
+ ActiveSupport.on_load(:active_record) do
34
+ if (bulk_alias = app.config.active_record_update_in_bulk.values_table_alias)
35
+ unless bulk_alias.instance_of?(String) && !bulk_alias.empty?
36
+ raise ArgumentError, "values_table_alias must be a non-empty String"
37
+ end
38
+ ActiveRecord::UpdateInBulk::Builder.values_table_name = bulk_alias
36
39
  end
37
- ActiveRecord::UpdateInBulk::Builder.values_table_name = bulk_alias
38
40
  end
39
41
  end
40
42
 
41
- initializer "active_record_update_in_bulk.ignore_scope_order", after: :load_config_initializers do |app|
42
- ignore_scope_order = app.config.active_record_update_in_bulk.ignore_scope_order
43
- unless ignore_scope_order == true || ignore_scope_order == false
44
- raise ArgumentError, "ignore_scope_order must be true or false"
43
+ initializer "active_record_update_in_bulk.ignore_scope_order" do |app|
44
+ ActiveSupport.on_load(:active_record) do
45
+ ignore_scope_order = app.config.active_record_update_in_bulk.ignore_scope_order
46
+ unless ignore_scope_order == true || ignore_scope_order == false
47
+ raise ArgumentError, "ignore_scope_order must be true or false"
48
+ end
49
+ ActiveRecord::UpdateInBulk::Builder.ignore_scope_order = ignore_scope_order
45
50
  end
46
- ActiveRecord::UpdateInBulk::Builder.ignore_scope_order = ignore_scope_order
47
51
  end
48
52
  end
49
53
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module UpdateInBulk
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.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.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bruno Carvalho
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '8.0'
26
- - !ruby/object:Gem::Dependency
27
- name: rake
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :development
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
26
  description: Introduces update_in_bulk(), a method to update many records in a table
41
27
  with different values in a single SQL statement.
42
28
  email: