junk_drawer 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: