pluck_map 0.4.1 → 0.5.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
  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