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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ac225219ca58a7ff5126649f3fdec9e90e8350f2ba7cbb6ca12a8ca2a130758
4
- data.tar.gz: 1efe86c6841ac2ea5b0d262dd067df326d0be807d98605e9bf45e649859111cc
3
+ metadata.gz: 3316ae636b712a8656e2efce061b2055b6e75f19b59e0265f1139e312e88d7ec
4
+ data.tar.gz: bb75083e099c90e38f4b2a92840c197eb93a4c268b8c65917f4172c21e1b9f51
5
5
  SHA512:
6
- metadata.gz: a9054980e92bb48e728d8750816195693692c9718078e164d729a7577d53ba0d0221079b212baaa7501bb9b34874e27a386c186010b74a0c48462c5e53d76fb7
7
- data.tar.gz: 6af0252a0c7163df76266f8104c2dd9957a5a3c3e4317dc6bd4d95411cac271ecd3b9eb35edd1ea1c1da122a41b250a1ca87c67eb2787e080963ca259738111c
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
- This tends to be about one order of magnitude faster the first example.
37
+ In [a simple benchmark](https://github.com/boblail/pluck_map/blob/master/test/benchmarks.rb), the second example is 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
- It is straightforward but verbose (we repeat the attribute names at least three times) and changing a block like this produces noisy diffs:
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
- And when we're presenting large or complex objects, the list of attributes we send to `pluck` or arguments we declare in the `map` block can get unwieldy!
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::Presenter.new do |q|
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::Presenter.new do |q|
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 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
@@ -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
- builder = self.new
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
- builder.instance_variable_get(:@attributes).freeze
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 PluckMap::Attribute.new(attribute_name, options)
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
@@ -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
- @attributes = PluckMap::AttributeBuilder.build(&block)
12
-
13
- @attributes_by_id = {}
14
- @selects = []
15
- attributes.each do |attribute|
16
- attribute.indexes = attribute.selects.map do |select|
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 with no replacement"
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
@@ -1 +1,3 @@
1
+ require "pluck_map/presenters/to_csv"
1
2
  require "pluck_map/presenters/to_h"
3
+ require "pluck_map/presenters/to_json"
@@ -1,3 +1,3 @@
1
1
  module PluckMapPresenter
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
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.1
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-04-27 00:00:00.000000000 Z
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