junk_drawer 1.2.1 → 1.3.0

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
  SHA1:
3
- metadata.gz: 7f11e6f56418b14bf2a44d91ffadff154fcf79a6
4
- data.tar.gz: 2240a549479b367d864b343fc3d5106821fa074c
3
+ metadata.gz: fa1bc248af13f000203f8c72364c382c44de203a
4
+ data.tar.gz: 05ad7bf2b95cb6630eab0129811dcb68cd8e1e6b
5
5
  SHA512:
6
- metadata.gz: b1692ef49d130c6b5e761fe1587871cbc3ebced928965eb98464dfc88ec0e436fa80de0837f4fa87162f2d162c3aba70cd571e72d3844380a5c1811a0bbadb7e
7
- data.tar.gz: baaa55a6d653c4f514b0850777e31616f93af87758bfb592f629240934e7f7675eb5fa03780e8933618bc9140f8a399bf969138b1ce0ce391ac8bf28100effdd
6
+ metadata.gz: 99d8ee5d88bc1bcd7d3253fc4dbac3be7be6418ede9a772bc4963233b0785344429ee40a2f8b2078e8d51e678aead1701eeba99ed4227ade2206b79e7af1912b
7
+ data.tar.gz: 108ff56c6ed5bbe24e81edcfa099e0ae7a55cada04469a0e9900d76b4b3623b64f334f39eaa52ed048b18811bf7fd8e907b83e95f7169d9f745de2ee93f4673b
data/.rubocop.yml CHANGED
@@ -5,6 +5,18 @@ AllCops:
5
5
  TargetRubyVersion: 2.3
6
6
  DisplayCopNames: true
7
7
 
8
+ Lint/AmbiguousBlockAssociation:
9
+ Exclude:
10
+ - spec/**/*_spec.rb
11
+
12
+ Metrics/AbcSize:
13
+ Exclude:
14
+ - lib/junk_drawer/rails/bulk_updatable.rb
15
+
16
+ Metrics/MethodLength:
17
+ Exclude:
18
+ - lib/junk_drawer/rails/bulk_updatable.rb
19
+
8
20
  Metrics/BlockLength:
9
21
  Exclude:
10
22
  - junk_drawer.gemspec
@@ -59,6 +71,11 @@ Style/OptionHash:
59
71
  Style/Send:
60
72
  Enabled: true
61
73
 
74
+ RSpec/FilePath:
75
+ IgnoreMethods: true
76
+ Exclude:
77
+ - spec/junk_drawer/rails/bulk_updatable_spec.rb
78
+
62
79
  RSpec/VerifiedDoubles:
63
80
  Enabled: true
64
81
 
data/.travis.yml CHANGED
@@ -2,7 +2,11 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.3.1
5
+ addons:
6
+ postgresql: '9.4'
5
7
  before_install: gem install bundler -v 1.13.6
8
+ before_script:
9
+ - createdb junk_drawer_test -U postgres
6
10
  script:
7
11
  - bundle exec rubocop
8
12
  - bundle exec rake
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # JunkDrawer
2
2
 
3
- `JunkDrawer` is a gem providing exactly one (and someday more) random utility
4
- that is commonly useful across projects.
3
+ `JunkDrawer` is a gem providing a handful of random utility that are commonly
4
+ useful across projects.
5
5
 
6
6
  ## Installation
7
7
 
@@ -19,6 +19,18 @@ Or install it yourself as:
19
19
 
20
20
  $ gem install junk_drawer
21
21
 
22
+ If you want to include the Rails utilities, in your Gemfile you can instead use:
23
+
24
+ ```ruby
25
+ gem 'junk_drawer', require: 'junk_drawer/rails'
26
+ ```
27
+
28
+ ### Contents
29
+
30
+ - [JunkDrawer::Callable](#junkdrawercallable)
31
+ - [JunkDrawer::Notifier](#junkdrawernotifier)
32
+ - [JunkDrawer::BulkUpdatable](#junkdrawerbulkupdatable)
33
+
22
34
  ## Usage
23
35
 
24
36
  ### JunkDrawer::Callable
@@ -134,6 +146,68 @@ config.after_initialize do
134
146
  end
135
147
  ```
136
148
 
149
+ -------------------------------------------------------------------------------
150
+
151
+ ## Rails
152
+
153
+ For Rails specific tools, instead of requiring `'junk_drawer'`, you can require
154
+ `'junk_drawer/rails'`. This will pull in both the plain Ruby and the Rails
155
+ specific utilities.
156
+
157
+ ### JunkDrawer::BulkUpdatable
158
+
159
+ `JunkDrawer::BulkUpdatable` is a utility to enable bulk updating of
160
+ `ActiveRecord` models. To enable it, extend in your models:
161
+
162
+ ```ruby
163
+ class MyModel < ApplicationRecord
164
+ extend JunkDrawer::BulkUpdatable
165
+ end
166
+ ```
167
+
168
+ If you want to enable it for all models, you can also add it to your
169
+ `ApplicationModel` class:
170
+
171
+ ```ruby
172
+ class ApplicationRecord
173
+ self.abstract_class = true
174
+ extend JunkDrawer::BulkUpdatable
175
+ end
176
+ ```
177
+
178
+ To make use of it, you can pass an array of records into the `.bulk_update`
179
+ class method on your model:
180
+
181
+ ```ruby
182
+ my_model_1 = MyModel.find(1)
183
+ my_model_1.name = 'Jabba'
184
+ my_model_2 = MyModel.find(2)
185
+ my_model_2.name = 'JarJar'
186
+
187
+ MyModel.bulk_update([my_model_1, my_model_2])
188
+ ```
189
+
190
+ This will generate a single SQL query to update both of the records in the
191
+ database.
192
+
193
+ #### Caveats
194
+
195
+ - Right now this only supports PostgreSQL. PR's welcome!
196
+ - It also only supports basic data types (including `hstore` and `jsonb`) for
197
+ your columns, so if you've got something weird you may have a bad time. Also
198
+ PR's welcome!
199
+ - General advice: if you're updating many thousands of records at the same
200
+ time, you may still run into some performance bottlenecks. When you're
201
+ dealing with massive amounts of data, we suggest pairing
202
+ `JunkDrawer::BulkUpdatable` with Rails' built-in `find_in_batches`:
203
+
204
+ ```ruby
205
+ MyModel.find_in_batches(batch_size: 250) do |batch|
206
+ batch.each { |my_model| my_model.name = 'Jar' * rand(100) }
207
+ MyModel.bulk_update(batch)
208
+ end
209
+ ```
210
+
137
211
  ## Development
138
212
 
139
213
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
data/junk_drawer.gemspec CHANGED
@@ -21,14 +21,18 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.required_ruby_version = '>= 2.1'
23
23
 
24
+ spec.add_development_dependency 'activerecord', '~> 4.2'
25
+ spec.add_development_dependency 'activesupport', '~> 4.2'
24
26
  spec.add_development_dependency 'bundler', '~> 1.13'
25
27
  spec.add_development_dependency 'guard', '~> 2.14'
26
28
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
27
29
  spec.add_development_dependency 'guard-rubocop', '~> 1.2'
30
+ spec.add_development_dependency 'pg', '~> 0.20'
28
31
  spec.add_development_dependency 'pry', '~> 0.10'
29
- spec.add_development_dependency 'rake', '~> 10.0'
32
+ spec.add_development_dependency 'rake', '~> 12.0'
30
33
  spec.add_development_dependency 'rspec', '~> 3.0'
31
- spec.add_development_dependency 'rubocop', '~> 0.45.0'
32
- spec.add_development_dependency 'rubocop-rspec', '~> 1.8.0'
33
- spec.add_development_dependency 'simplecov', '~> 0.12'
34
+ spec.add_development_dependency 'rubocop', '~> 0.48.1'
35
+ spec.add_development_dependency 'rubocop-rspec', '~> 1.15.0'
36
+ spec.add_development_dependency 'simplecov', '~> 0.14'
37
+ spec.add_development_dependency 'with_model', '~> 2.0'
34
38
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+ require 'active_record'
5
+ require 'active_record/connection_adapters/postgresql_adapter'
6
+
7
+ module JunkDrawer
8
+ # module to allow bulk updates for `ActiveRecord` models
9
+ module BulkUpdatable
10
+ ATTRIBUTE_TYPE_TO_POSTGRES_CAST = {
11
+ datetime: '::timestamp',
12
+ hstore: '::hstore',
13
+ boolean: '::boolean',
14
+ jsonb: '::jsonb',
15
+ }.freeze
16
+
17
+ POSTGRES_VALUE_CASTERS = {
18
+ hstore: ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Hstore.new,
19
+ jsonb: ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb.new,
20
+ }.freeze
21
+
22
+ def bulk_update(objects)
23
+ objects = objects.select(&:changed?)
24
+ return unless objects.any?
25
+
26
+ changed_attributes = extract_changed_attributes(objects)
27
+ query = build_query_for(objects, changed_attributes)
28
+ connection.execute(query)
29
+ end
30
+
31
+ private
32
+
33
+ def extract_changed_attributes(objects)
34
+ now = Time.zone.now
35
+ objects.each { |object| object.updated_at = now }
36
+
37
+ objects.flat_map(&:changed).uniq
38
+ end
39
+
40
+ def build_query_for(objects, attributes)
41
+ object_values = objects.map do |object|
42
+ sanitized_values(object, attributes)
43
+ end.join(', ')
44
+
45
+ assignment_query = attributes.map do |attribute|
46
+ quoted_column_name = connection.quote_column_name(attribute)
47
+ "#{quoted_column_name} = tmp_table.#{quoted_column_name}"
48
+ end.join(', ')
49
+
50
+ <<-SQL
51
+ UPDATE #{table_name}
52
+ SET #{assignment_query}
53
+ FROM (VALUES #{object_values}) AS tmp_table(id, #{attributes.join(', ')})
54
+ WHERE #{table_name}.id = tmp_table.id
55
+ SQL
56
+ end
57
+
58
+ def sanitized_values(object, attributes)
59
+ postgres_types = attributes.map do |attribute|
60
+ attribute_type = columns_hash[attribute.to_s].type
61
+ "?#{ATTRIBUTE_TYPE_TO_POSTGRES_CAST[attribute_type]}"
62
+ end
63
+
64
+ postgres_values = attributes.map do |attribute|
65
+ value = object[attribute]
66
+ attribute_type = columns_hash[attribute.to_s].type
67
+ caster = POSTGRES_VALUE_CASTERS[attribute_type]
68
+
69
+ caster ? caster.type_cast_for_database(value) : value
70
+ end
71
+
72
+ sanitize_sql_array(
73
+ ["(?, #{postgres_types.join(', ')})", object.id, *postgres_values],
74
+ )
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../junk_drawer'
4
+ require_relative 'rails/bulk_updatable'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JunkDrawer
4
- VERSION = '1.2.1'
4
+ VERSION = '1.3.0'
5
5
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: junk_drawer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Fletcher
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-04-19 00:00:00.000000000 Z
11
+ date: 2017-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +94,20 @@ dependencies:
66
94
  - - "~>"
67
95
  - !ruby/object:Gem::Version
68
96
  version: '1.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pg
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.20'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.20'
69
111
  - !ruby/object:Gem::Dependency
70
112
  name: pry
71
113
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +128,14 @@ dependencies:
86
128
  requirements:
87
129
  - - "~>"
88
130
  - !ruby/object:Gem::Version
89
- version: '10.0'
131
+ version: '12.0'
90
132
  type: :development
91
133
  prerelease: false
92
134
  version_requirements: !ruby/object:Gem::Requirement
93
135
  requirements:
94
136
  - - "~>"
95
137
  - !ruby/object:Gem::Version
96
- version: '10.0'
138
+ version: '12.0'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: rspec
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -114,42 +156,56 @@ dependencies:
114
156
  requirements:
115
157
  - - "~>"
116
158
  - !ruby/object:Gem::Version
117
- version: 0.45.0
159
+ version: 0.48.1
118
160
  type: :development
119
161
  prerelease: false
120
162
  version_requirements: !ruby/object:Gem::Requirement
121
163
  requirements:
122
164
  - - "~>"
123
165
  - !ruby/object:Gem::Version
124
- version: 0.45.0
166
+ version: 0.48.1
125
167
  - !ruby/object:Gem::Dependency
126
168
  name: rubocop-rspec
127
169
  requirement: !ruby/object:Gem::Requirement
128
170
  requirements:
129
171
  - - "~>"
130
172
  - !ruby/object:Gem::Version
131
- version: 1.8.0
173
+ version: 1.15.0
132
174
  type: :development
133
175
  prerelease: false
134
176
  version_requirements: !ruby/object:Gem::Requirement
135
177
  requirements:
136
178
  - - "~>"
137
179
  - !ruby/object:Gem::Version
138
- version: 1.8.0
180
+ version: 1.15.0
139
181
  - !ruby/object:Gem::Dependency
140
182
  name: simplecov
141
183
  requirement: !ruby/object:Gem::Requirement
142
184
  requirements:
143
185
  - - "~>"
144
186
  - !ruby/object:Gem::Version
145
- version: '0.12'
187
+ version: '0.14'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '0.14'
195
+ - !ruby/object:Gem::Dependency
196
+ name: with_model
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '2.0'
146
202
  type: :development
147
203
  prerelease: false
148
204
  version_requirements: !ruby/object:Gem::Requirement
149
205
  requirements:
150
206
  - - "~>"
151
207
  - !ruby/object:Gem::Version
152
- version: '0.12'
208
+ version: '2.0'
153
209
  description:
154
210
  email:
155
211
  - lobatifricha@gmail.com
@@ -176,6 +232,8 @@ files:
176
232
  - lib/junk_drawer/notifier/honeybadger_strategy.rb
177
233
  - lib/junk_drawer/notifier/null_strategy.rb
178
234
  - lib/junk_drawer/notifier/raise_strategy.rb
235
+ - lib/junk_drawer/rails.rb
236
+ - lib/junk_drawer/rails/bulk_updatable.rb
179
237
  - lib/junk_drawer/version.rb
180
238
  homepage: https://github.com/mockdeep/junk_drawer
181
239
  licenses: