junk_drawer 2.0.0 → 2.1.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 +4 -4
- data/.rubocop_todo.yml +11 -11
- data/README.md +3 -2
- data/lib/junk_drawer/rails/bulk_updatable.rb +80 -22
- data/lib/junk_drawer/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af0c9f7f4aa7cd06e4d7f6aafb47263111d60d8039f2da8abc3a479d572a7d2a
|
4
|
+
data.tar.gz: a3b3420460631eb69b70771f4057cf3ed9980a41fb17aeda4a1cd8ec59d6e708
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6fc9b47023c4e1ee539191ec8862cbf982b62fb4a4e3f13dc9966d42bb00e20aa560858855deb6735bec907bcf61eefbbe7de35865451d0cd8724ad569ee2e6
|
7
|
+
data.tar.gz: eca53a28e48bd935474769d0cf5f56123caa2853f4ec8fb83f08b444a7f2d975b1f6aec9a33df058bb81d33a1a31e087e885ccefff03da329d7b03f406eb2ee6
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 400`
|
3
|
-
# on 2023-
|
3
|
+
# on 2023-05-16 20:46:21 UTC using RuboCop version 1.49.0.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
@@ -36,7 +36,7 @@ Layout/LineEndStringConcatenationIndentation:
|
|
36
36
|
# SupportedStyles: aligned, indented, indented_relative_to_receiver
|
37
37
|
Layout/MultilineMethodCallIndentation:
|
38
38
|
Exclude:
|
39
|
-
- 'spec/
|
39
|
+
- 'spec/support/shared_examples/bulk_updatable_model.rb'
|
40
40
|
|
41
41
|
# Offense count: 14
|
42
42
|
# Configuration parameters: AllowedMethods.
|
@@ -46,7 +46,7 @@ Lint/ConstantDefinitionInBlock:
|
|
46
46
|
- 'spec/junk_drawer/callable_spec.rb'
|
47
47
|
- 'spec/junk_drawer/notifier/honeybadger_strategy_spec.rb'
|
48
48
|
- 'spec/junk_drawer/notifier/null_strategy_spec.rb'
|
49
|
-
- 'spec/
|
49
|
+
- 'spec/support/shared_examples/bulk_updatable_model.rb'
|
50
50
|
|
51
51
|
# Offense count: 1
|
52
52
|
# Configuration parameters: MaximumRangeSize.
|
@@ -65,6 +65,12 @@ Metrics/CyclomaticComplexity:
|
|
65
65
|
Exclude:
|
66
66
|
- 'lib/junk_drawer/rails/bulk_updatable.rb'
|
67
67
|
|
68
|
+
# Offense count: 1
|
69
|
+
# Configuration parameters: CountComments, Max, CountAsOne.
|
70
|
+
Metrics/ModuleLength:
|
71
|
+
Exclude:
|
72
|
+
- 'lib/junk_drawer/rails/bulk_updatable.rb'
|
73
|
+
|
68
74
|
# Offense count: 1
|
69
75
|
# This cop supports safe autocorrection (--autocorrect).
|
70
76
|
# Configuration parameters: EnforcedStyle.
|
@@ -88,7 +94,7 @@ RSpec/LeakyConstantDeclaration:
|
|
88
94
|
- 'spec/junk_drawer/callable_spec.rb'
|
89
95
|
- 'spec/junk_drawer/notifier/honeybadger_strategy_spec.rb'
|
90
96
|
- 'spec/junk_drawer/notifier/null_strategy_spec.rb'
|
91
|
-
- 'spec/
|
97
|
+
- 'spec/support/shared_examples/bulk_updatable_model.rb'
|
92
98
|
|
93
99
|
# Offense count: 1
|
94
100
|
RSpec/StubbedMock:
|
@@ -102,10 +108,4 @@ RSpec/StubbedMock:
|
|
102
108
|
Style/HashAsLastArrayItem:
|
103
109
|
Exclude:
|
104
110
|
- 'spec/junk_drawer/notifier/honeybadger_strategy_spec.rb'
|
105
|
-
- 'spec/
|
106
|
-
|
107
|
-
# Offense count: 2
|
108
|
-
# This cop supports safe autocorrection (--autocorrect).
|
109
|
-
Style/RedundantConstantBase:
|
110
|
-
Exclude:
|
111
|
-
- 'spec/junk_drawer/rails/bulk_updatable_spec.rb'
|
111
|
+
- 'spec/support/shared_examples/bulk_updatable_model.rb'
|
data/README.md
CHANGED
@@ -219,7 +219,8 @@ MyModel.bulk_update([my_model_1, my_model_2])
|
|
219
219
|
```
|
220
220
|
|
221
221
|
This will generate a single SQL query to update both of the records in the
|
222
|
-
database.
|
222
|
+
database. If `prepared_statements` is set to true, `BulkUpdatable` will generate
|
223
|
+
queries that use bind parameters.
|
223
224
|
|
224
225
|
#### Caveats
|
225
226
|
|
@@ -255,7 +256,7 @@ In order to run tests against different Rails versions, you can use
|
|
255
256
|
`BUNDLE_GEMFILE`:
|
256
257
|
|
257
258
|
```sh
|
258
|
-
$ BUNDLE_GEMFILE=gemfiles/
|
259
|
+
$ BUNDLE_GEMFILE=gemfiles/rails_7.0.gems rake spec
|
259
260
|
```
|
260
261
|
|
261
262
|
## Contributing
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'active_support/all'
|
4
4
|
require 'active_record'
|
5
5
|
require 'active_record/connection_adapters/postgresql_adapter'
|
6
|
+
require 'active_record/relation/query_attribute'
|
6
7
|
|
7
8
|
module JunkDrawer
|
8
9
|
# module to allow bulk updates for `ActiveRecord` models
|
@@ -11,15 +12,35 @@ module JunkDrawer
|
|
11
12
|
objects = objects.select(&:changed?)
|
12
13
|
return unless objects.any?
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
if connection.prepared_statements
|
16
|
+
build_and_exec_prepared_query(objects)
|
17
|
+
else
|
18
|
+
build_and_exec_unprepared_query(objects)
|
19
|
+
end
|
18
20
|
objects.each(&:changes_applied)
|
19
21
|
end
|
20
22
|
|
21
23
|
private
|
22
24
|
|
25
|
+
def build_and_exec_prepared_query(objects)
|
26
|
+
unique_objects = uniquify_and_merge(objects)
|
27
|
+
changed_attributes = extract_changed_attributes(unique_objects)
|
28
|
+
attributes = ['id'] + changed_attributes
|
29
|
+
|
30
|
+
unique_objects.each_slice(batch_size(changed_attributes)) do |batch|
|
31
|
+
query = build_prepared_query_for(batch, attributes, changed_attributes)
|
32
|
+
values = values_for_objects(batch, attributes)
|
33
|
+
connection.exec_query(query, "#{name} Bulk Update", values, prepare: true)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_and_exec_unprepared_query(objects)
|
38
|
+
unique_objects = uniquify_and_merge(objects)
|
39
|
+
changed_attributes = extract_changed_attributes(unique_objects)
|
40
|
+
query = build_unprepared_query_for(unique_objects, changed_attributes)
|
41
|
+
connection.execute(query)
|
42
|
+
end
|
43
|
+
|
23
44
|
def uniquify_and_merge(objects)
|
24
45
|
grouped_objects = objects.group_by(&:id).values
|
25
46
|
grouped_objects.each do |group|
|
@@ -40,19 +61,20 @@ module JunkDrawer
|
|
40
61
|
objects.each { |object| object.updated_at = now }
|
41
62
|
|
42
63
|
changed_attributes = objects.flat_map(&:changed).uniq
|
43
|
-
|
44
|
-
column_names & changed_attributes
|
45
|
-
else
|
46
|
-
# to remove virtual columns from jsonb_accessor 0.3.3
|
47
|
-
columns.select(&:sql_type).map(&:name) & changed_attributes
|
48
|
-
end
|
64
|
+
column_names & changed_attributes
|
49
65
|
end
|
50
66
|
|
51
|
-
def
|
52
|
-
object_values = objects.map
|
53
|
-
|
54
|
-
|
67
|
+
def build_unprepared_query_for(objects, attributes)
|
68
|
+
object_values = objects.map { |object| sanitized_values(object, attributes) }
|
69
|
+
build_query_for(attributes, object_values.join(', '))
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_prepared_query_for(objects, attributes, changed_attributes)
|
73
|
+
object_placeholders = build_placeholders(objects, attributes)
|
74
|
+
build_query_for(changed_attributes, object_placeholders)
|
75
|
+
end
|
55
76
|
|
77
|
+
def build_query_for(attributes, values)
|
56
78
|
assignment_query = attributes.map do |attribute|
|
57
79
|
quoted_column_name = connection.quote_column_name(attribute)
|
58
80
|
"#{quoted_column_name} = tmp_table.#{quoted_column_name}"
|
@@ -60,11 +82,50 @@ module JunkDrawer
|
|
60
82
|
|
61
83
|
"UPDATE #{table_name} " \
|
62
84
|
"SET #{assignment_query} " \
|
63
|
-
"FROM (VALUES #{
|
85
|
+
"FROM (VALUES #{values}) " \
|
64
86
|
"AS tmp_table(id, #{attributes.join(', ')}) " \
|
65
87
|
"WHERE #{table_name}.id = tmp_table.id"
|
66
88
|
end
|
67
89
|
|
90
|
+
def build_placeholders(objects, attributes)
|
91
|
+
index = 0
|
92
|
+
objects.map do
|
93
|
+
attribute_placeholders = attributes.map do |attribute|
|
94
|
+
index += 1
|
95
|
+
attribute_placeholder(attribute, index)
|
96
|
+
end.join(', ')
|
97
|
+
|
98
|
+
"(#{attribute_placeholders})"
|
99
|
+
end.join(', ')
|
100
|
+
end
|
101
|
+
|
102
|
+
def attribute_placeholder(attribute, index)
|
103
|
+
# AR internal `columns_hash`
|
104
|
+
column = columns_hash[attribute.to_s]
|
105
|
+
|
106
|
+
type_cast = "::#{column.sql_type}"
|
107
|
+
type_cast = "#{type_cast}[]" if column.array
|
108
|
+
|
109
|
+
"$#{index}#{type_cast}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def values_for_objects(objects, attributes)
|
113
|
+
objects.flat_map { |object| values_for_object(object, attributes) }
|
114
|
+
end
|
115
|
+
|
116
|
+
def values_for_object(object, attributes)
|
117
|
+
attributes.map do |attribute|
|
118
|
+
value = object[attribute]
|
119
|
+
|
120
|
+
# AR internal `columns_hash`
|
121
|
+
column = columns_hash[attribute.to_s]
|
122
|
+
|
123
|
+
# AR internal `type_for_attribute`
|
124
|
+
type = type_for_attribute(column.name)
|
125
|
+
ActiveRecord::Relation::QueryAttribute.new(column.name, value, type)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
68
129
|
def sanitized_values(object, attributes)
|
69
130
|
postgres_values = attributes.map do |attribute|
|
70
131
|
value = object[attribute]
|
@@ -77,18 +138,15 @@ module JunkDrawer
|
|
77
138
|
type_cast = "::#{column.sql_type}"
|
78
139
|
type_cast = "#{type_cast}[]" if column.array
|
79
140
|
|
80
|
-
"#{connection.quote(
|
141
|
+
"#{connection.quote(type.serialize(value))}#{type_cast}"
|
81
142
|
end
|
82
143
|
|
83
144
|
"(#{[object.id, *postgres_values].join(', ')})"
|
84
145
|
end
|
85
146
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
else
|
90
|
-
type.type_cast_for_database(value)
|
91
|
-
end
|
147
|
+
def batch_size(attribute_names)
|
148
|
+
max_bind_params = connection.__send__(:bind_params_length)
|
149
|
+
max_bind_params / attribute_names.length
|
92
150
|
end
|
93
151
|
end
|
94
152
|
end
|
data/lib/junk_drawer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: junk_drawer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Fletcher
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: guard
|
@@ -164,7 +164,7 @@ dependencies:
|
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '2.0'
|
167
|
-
description:
|
167
|
+
description:
|
168
168
|
email:
|
169
169
|
- lobatifricha@gmail.com
|
170
170
|
executables: []
|
@@ -199,7 +199,7 @@ homepage: https://github.com/thread-pond/junk_drawer
|
|
199
199
|
licenses:
|
200
200
|
- MIT
|
201
201
|
metadata: {}
|
202
|
-
post_install_message:
|
202
|
+
post_install_message:
|
203
203
|
rdoc_options: []
|
204
204
|
require_paths:
|
205
205
|
- lib
|
@@ -214,8 +214,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
214
214
|
- !ruby/object:Gem::Version
|
215
215
|
version: '0'
|
216
216
|
requirements: []
|
217
|
-
rubygems_version: 3.4.
|
218
|
-
signing_key:
|
217
|
+
rubygems_version: 3.4.10
|
218
|
+
signing_key:
|
219
219
|
specification_version: 4
|
220
220
|
summary: random useful Ruby utilities
|
221
221
|
test_files: []
|