pluck_map 0.4.1 → 0.5.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +5 -7
- data/Rakefile +18 -1
- data/lib/pluck_map/attribute.rb +3 -2
- data/lib/pluck_map/attribute_builder.rb +10 -7
- data/lib/pluck_map/attributes.rb +63 -0
- data/lib/pluck_map/model_context.rb +14 -0
- data/lib/pluck_map/presenter.rb +22 -17
- data/lib/pluck_map/presenters/to_csv.rb +29 -0
- data/lib/pluck_map/presenters/to_json.rb +23 -0
- data/lib/pluck_map/presenters.rb +2 -0
- data/lib/pluck_map/version.rb +1 -1
- data/lib/pluck_map.rb +5 -0
- data/pluck_map.gemspec +3 -0
- metadata +48 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3316ae636b712a8656e2efce061b2055b6e75f19b59e0265f1139e312e88d7ec
|
4
|
+
data.tar.gz: bb75083e099c90e38f4b2a92840c197eb93a4c268b8c65917f4172c21e1b9f51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2801ad881bdc370044fe2c997f7f3d1c137ae757af7aabc4bcc0d3c71e5a412ea136f95a769968aa4972e6d8385ccd6f961c265d8e6946363595bbc3139aae2
|
7
|
+
data.tar.gz: 1ef71fbbd7f3fcdb4494087fc537db3962fb2529373272edb3c96a0a71481c0b18f69f43201cde511640aa5970f0b7a545dfd96e3dabab32f2e89d07a07c2b87
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
## v0.5.0 (2019 May 1)
|
2
|
+
|
3
|
+
* REFACTOR: Introduced a new syntax for defining PluckMap Presenters and deprecated
|
4
|
+
calling `PluckMap::Presenter.new` with a block
|
5
|
+
* FEATURE: Add `to_json` presenter method (@boblail)
|
6
|
+
* FEATURE: Add `to_csv` presenter method (@boblail)
|
7
|
+
|
1
8
|
## v0.4.1 (2019 Apr 26)
|
2
9
|
|
3
10
|
* FIX: Use `PluckMap::NullLogger` when `Rails.logger` is `nil` (@boblail)
|
data/README.md
CHANGED
@@ -34,9 +34,9 @@ We can skip that unnecessary instantiation by using `pluck`:
|
|
34
34
|
end
|
35
35
|
```
|
36
36
|
|
37
|
-
|
37
|
+
In [a simple benchmark](https://github.com/boblail/pluck_map/blob/master/test/benchmarks.rb), the second example is 3× faster than the first and allocates half as much memory. :rocket: (Mileage may vary, of course, but in real applications with more complex models, I've gotten more like a 10× improvement at bottlenecks.)
|
38
38
|
|
39
|
-
|
39
|
+
One drawback to this technique is its verbosity — we repeat the attribute names at least three times and changes to blocks like this make for noisy diffs:
|
40
40
|
|
41
41
|
```diff
|
42
42
|
def index
|
@@ -53,14 +53,12 @@ It is straightforward but verbose (we repeat the attribute names at least three
|
|
53
53
|
end
|
54
54
|
```
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
The `PluckMap::Presenter` is simply a shorthand for generating the above pluck-map pattern. Using it, we could write our example like this:
|
56
|
+
`PluckMap::Presenter` gives us a shorthand for generating the above pluck-map pattern. Using it, we could write our example like this:
|
59
57
|
|
60
58
|
```ruby
|
61
59
|
def index
|
62
60
|
messages = Message.created_by(current_user).after(3.weeks.ago)
|
63
|
-
presenter = PluckMap
|
61
|
+
presenter = PluckMap[Message].define do |q|
|
64
62
|
q.id
|
65
63
|
q.postedAt select: :created_at
|
66
64
|
q.text
|
@@ -77,7 +75,7 @@ This DSL also makes it easy to make fields optional:
|
|
77
75
|
```diff
|
78
76
|
def index
|
79
77
|
messages = Message.created_by(current_user).after(3.weeks.ago)
|
80
|
-
presenter = PluckMap
|
78
|
+
presenter = PluckMap[Message].define do |q|
|
81
79
|
q.id
|
82
80
|
q.postedAt select: :created_at
|
83
81
|
q.text
|
data/Rakefile
CHANGED
@@ -36,7 +36,17 @@ namespace :test do
|
|
36
36
|
t.libs << "test"
|
37
37
|
t.libs << "lib"
|
38
38
|
t.test_files = FileList["test/**/*_test.rb"]
|
39
|
-
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
namespace :benchmark do
|
44
|
+
ADAPTERS.each do |adapter|
|
45
|
+
desc "Run benchmarks on #{adapter}"
|
46
|
+
task adapter => "db:#{adapter}:env" do
|
47
|
+
load File.expand_path("test/benchmarks.rb", __dir__)
|
48
|
+
end
|
49
|
+
end
|
40
50
|
end
|
41
51
|
|
42
52
|
def run_without_aborting(*tasks)
|
@@ -46,6 +56,7 @@ def run_without_aborting(*tasks)
|
|
46
56
|
puts task
|
47
57
|
Rake::Task[task].invoke
|
48
58
|
rescue Exception
|
59
|
+
puts "\e[31m#{$!.class}: #{$!.message}\e[0m"
|
49
60
|
errors << task
|
50
61
|
end
|
51
62
|
|
@@ -58,5 +69,11 @@ task :test do
|
|
58
69
|
run_without_aborting(*tasks)
|
59
70
|
end
|
60
71
|
|
72
|
+
desc "Run #{ADAPTERS.join(', ')} benchmarks"
|
73
|
+
task :benchmark do
|
74
|
+
tasks = ADAPTERS.map { |adapter| "benchmark:#{adapter}" }
|
75
|
+
run_without_aborting(*tasks)
|
76
|
+
end
|
77
|
+
|
61
78
|
desc "Run #{ADAPTERS.join(', ')} tests by default"
|
62
79
|
task default: :test
|
data/lib/pluck_map/attribute.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module PluckMap
|
2
2
|
class Attribute
|
3
|
-
attr_reader :id, :selects, :name, :value, :block
|
3
|
+
attr_reader :id, :model, :selects, :name, :value, :block
|
4
4
|
attr_accessor :indexes
|
5
5
|
|
6
|
-
def initialize(id, options={})
|
6
|
+
def initialize(id, model, options={})
|
7
7
|
@id = id
|
8
|
+
@model = model
|
8
9
|
@selects = Array(options.fetch(:select, id))
|
9
10
|
@name = options.fetch(:as, id)
|
10
11
|
@block = options[:map]
|
@@ -1,26 +1,29 @@
|
|
1
1
|
require "pluck_map/attribute"
|
2
|
+
require "pluck_map/attributes"
|
2
3
|
|
3
4
|
module PluckMap
|
4
|
-
class AttributeBuilder
|
5
|
+
class AttributeBuilder < BasicObject
|
5
6
|
|
6
|
-
def self.build(&block)
|
7
|
-
|
7
|
+
def self.build(model:, &block)
|
8
|
+
attributes = []
|
9
|
+
builder = self.new(attributes, model)
|
8
10
|
if block.arity == 1
|
9
11
|
block.call(builder)
|
10
12
|
else
|
11
13
|
builder.instance_eval(&block)
|
12
14
|
end
|
13
|
-
|
15
|
+
Attributes.new(attributes)
|
14
16
|
end
|
15
17
|
|
16
|
-
def initialize
|
17
|
-
@attributes =
|
18
|
+
def initialize(attributes, model)
|
19
|
+
@attributes = attributes
|
20
|
+
@model = model
|
18
21
|
end
|
19
22
|
|
20
23
|
def method_missing(attribute_name, *args)
|
21
24
|
options = args.extract_options!
|
22
25
|
options[:value] = args.first unless args.empty?
|
23
|
-
@attributes.push
|
26
|
+
@attributes.push Attribute.new(attribute_name, @model, options)
|
24
27
|
:attribute_added
|
25
28
|
end
|
26
29
|
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module PluckMap
|
2
|
+
class Attributes
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :selects
|
6
|
+
|
7
|
+
def initialize(attributes)
|
8
|
+
@_attributes = attributes.freeze
|
9
|
+
@_attributes_by_id = {}
|
10
|
+
@selects = []
|
11
|
+
attributes.each do |attribute|
|
12
|
+
attribute.indexes = attribute.selects.map do |select|
|
13
|
+
selects.find_index(select) || begin
|
14
|
+
selects.push(select)
|
15
|
+
selects.length - 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
_attributes_by_id[attribute.id] = attribute
|
19
|
+
end
|
20
|
+
_attributes_by_id.freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
_attributes.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](index)
|
30
|
+
_attributes[index]
|
31
|
+
end
|
32
|
+
|
33
|
+
def length
|
34
|
+
_attributes.length
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
def by_id
|
40
|
+
_attributes_by_id
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
def ==(other)
|
46
|
+
return false if self.class != other.class
|
47
|
+
_attributes == other.send(:_attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
def hash
|
51
|
+
_attributes.hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def eql?(other)
|
55
|
+
return true if self.equal?(other)
|
56
|
+
return false if self.class != other.class
|
57
|
+
_attributes.eql?(other.send(:_attributes))
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
attr_reader :_attributes, :_attributes_by_id
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "pluck_map/presenter"
|
2
|
+
|
3
|
+
module PluckMap
|
4
|
+
class ModelContext
|
5
|
+
def initialize(model)
|
6
|
+
@model = model
|
7
|
+
end
|
8
|
+
|
9
|
+
def define(&block)
|
10
|
+
attributes = PluckMap::AttributeBuilder.build(model: @model, &block)
|
11
|
+
PluckMap::Presenter.new(@model, attributes)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/pluck_map/presenter.rb
CHANGED
@@ -3,23 +3,17 @@ require "pluck_map/presenters"
|
|
3
3
|
|
4
4
|
module PluckMap
|
5
5
|
class Presenter
|
6
|
-
include HashPresenter
|
6
|
+
include CsvPresenter, HashPresenter, JsonPresenter
|
7
7
|
|
8
|
-
attr_reader :attributes
|
8
|
+
attr_reader :model, :attributes
|
9
9
|
|
10
|
-
def initialize(&block)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
selects.find_index(select) || begin
|
18
|
-
selects.push(select)
|
19
|
-
selects.length - 1
|
20
|
-
end
|
21
|
-
end
|
22
|
-
attributes_by_id[attribute.id] = attribute
|
10
|
+
def initialize(model = nil, attributes = nil, &block)
|
11
|
+
if block_given?
|
12
|
+
puts "DEPRECATION WARNING: `PluckMap::Presenter.new` will be deprecated. Use `PluckMap[Model].define` instead."
|
13
|
+
@attributes = PluckMap::AttributeBuilder.build(model: nil, &block)
|
14
|
+
else
|
15
|
+
@model = model
|
16
|
+
@attributes = attributes
|
23
17
|
end
|
24
18
|
|
25
19
|
if respond_to?(:define_presenters!, true)
|
@@ -37,6 +31,10 @@ module PluckMap
|
|
37
31
|
protected
|
38
32
|
|
39
33
|
def pluck(query)
|
34
|
+
if model && query.model != model
|
35
|
+
raise ArgumentError, "Query for #{query.model} but #{model} expected"
|
36
|
+
end
|
37
|
+
|
40
38
|
# puts "\e[95m#{query.select(*selects).to_sql}\e[0m"
|
41
39
|
results = benchmark("pluck(#{query.table_name})") { query.pluck(*selects) }
|
42
40
|
return results unless block_given?
|
@@ -51,14 +49,21 @@ module PluckMap
|
|
51
49
|
end
|
52
50
|
|
53
51
|
private
|
54
|
-
attr_reader :attributes_by_id, :selects
|
55
52
|
|
56
53
|
def invoke(attribute_id, object)
|
57
54
|
attributes_by_id.fetch(attribute_id).apply(object)
|
58
55
|
end
|
59
56
|
|
57
|
+
def selects
|
58
|
+
attributes.selects
|
59
|
+
end
|
60
|
+
|
61
|
+
def attributes_by_id
|
62
|
+
attributes.by_id
|
63
|
+
end
|
64
|
+
|
60
65
|
def keys
|
61
|
-
puts "DEPRECATION WARNING: PluckMap::Presenter#keys is deprecated
|
66
|
+
puts "DEPRECATION WARNING: PluckMap::Presenter#keys is deprecated; use #selects instead"
|
62
67
|
selects
|
63
68
|
end
|
64
69
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module PluckMap
|
2
|
+
module CsvPresenter
|
3
|
+
|
4
|
+
def to_csv(query)
|
5
|
+
define_to_csv!
|
6
|
+
to_csv(query)
|
7
|
+
end
|
8
|
+
|
9
|
+
private def define_to_csv!
|
10
|
+
require "csv"
|
11
|
+
|
12
|
+
headers = CSV.generate_line(attributes.map(&:name))
|
13
|
+
ruby = <<-RUBY
|
14
|
+
def to_csv(query)
|
15
|
+
pluck(query) do |results|
|
16
|
+
rows = [#{headers.inspect}]
|
17
|
+
results.each_with_object(rows) do |values, rows|
|
18
|
+
values = Array(values)
|
19
|
+
rows << CSV.generate_line([#{attributes.map(&:to_ruby).join(", ")}])
|
20
|
+
end.join
|
21
|
+
end
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
# puts "\e[34m#{ruby}\e[0m" # <-- helps debugging PluckMapPresenter
|
25
|
+
class_eval ruby, __FILE__, __LINE__ - 7
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module PluckMap
|
4
|
+
module JsonPresenter
|
5
|
+
|
6
|
+
def to_json(query, json: default_json, **)
|
7
|
+
json.dump(to_h(query))
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def default_json
|
13
|
+
if defined?(MultiJson)
|
14
|
+
MultiJson
|
15
|
+
elsif defined?(Oj)
|
16
|
+
Oj
|
17
|
+
else
|
18
|
+
JSON
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/pluck_map/presenters.rb
CHANGED
data/lib/pluck_map/version.rb
CHANGED
data/lib/pluck_map.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
1
|
require "pluck_map/version"
|
2
|
+
require "pluck_map/model_context"
|
2
3
|
require "pluck_map/presenter"
|
3
4
|
require "pluck_map/null_logger"
|
4
5
|
|
5
6
|
module PluckMap
|
6
7
|
class << self
|
7
8
|
attr_accessor :logger
|
9
|
+
|
10
|
+
def [](model)
|
11
|
+
PluckMap::ModelContext.new(model)
|
12
|
+
end
|
8
13
|
end
|
9
14
|
|
10
15
|
@logger = (Rails.logger if defined?(Rails)) || PluckMap::NullLogger.new
|
data/pluck_map.gemspec
CHANGED
@@ -27,6 +27,9 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency "sqlite3"
|
28
28
|
spec.add_development_dependency "mysql2"
|
29
29
|
spec.add_development_dependency "pg"
|
30
|
+
spec.add_development_dependency "faker"
|
31
|
+
spec.add_development_dependency "benchmark-ips"
|
32
|
+
spec.add_development_dependency "benchmark-memory"
|
30
33
|
spec.add_development_dependency "database_cleaner"
|
31
34
|
spec.add_development_dependency "shoulda-context"
|
32
35
|
spec.add_development_dependency "rr"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pluck_map
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bob Lail
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -136,6 +136,48 @@ dependencies:
|
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: faker
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: benchmark-ips
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: benchmark-memory
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
139
181
|
- !ruby/object:Gem::Dependency
|
140
182
|
name: database_cleaner
|
141
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -217,10 +259,14 @@ files:
|
|
217
259
|
- lib/pluck_map.rb
|
218
260
|
- lib/pluck_map/attribute.rb
|
219
261
|
- lib/pluck_map/attribute_builder.rb
|
262
|
+
- lib/pluck_map/attributes.rb
|
263
|
+
- lib/pluck_map/model_context.rb
|
220
264
|
- lib/pluck_map/null_logger.rb
|
221
265
|
- lib/pluck_map/presenter.rb
|
222
266
|
- lib/pluck_map/presenters.rb
|
267
|
+
- lib/pluck_map/presenters/to_csv.rb
|
223
268
|
- lib/pluck_map/presenters/to_h.rb
|
269
|
+
- lib/pluck_map/presenters/to_json.rb
|
224
270
|
- lib/pluck_map/version.rb
|
225
271
|
- pluck_map.gemspec
|
226
272
|
homepage: https://github.com/boblail/pluck_map
|