pluck_map 0.3.0 → 0.4.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 +5 -0
- data/README.md +25 -20
- data/Rakefile +1 -0
- data/lib/pluck_map.rb +6 -0
- data/lib/pluck_map/attribute.rb +28 -19
- data/lib/pluck_map/attribute_builder.rb +28 -0
- data/lib/pluck_map/presenter.rb +29 -60
- data/lib/pluck_map/presenters.rb +1 -0
- data/lib/pluck_map/presenters/to_h.rb +22 -0
- data/lib/pluck_map/version.rb +1 -1
- data/pluck_map.gemspec +1 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c84ee8f4db192127a69450c81189315686ee14b23e65bfd08d49ee14c09e9d6c
|
4
|
+
data.tar.gz: d00b45929552234fe71457bf40649f8cdf6344198d278a4168760bf694c86e65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35eb0d21063e78cf8ea5968e93faa715fffba2198f4b54cfa5f0ade709bd375741c1b29dc27d03cdc8a5bbce99b5afa53fdc6e34f1fdabcfecb7f3a7cb117410
|
7
|
+
data.tar.gz: 909ab9afe1a0818bddaecb2406d8e69a5c4d6e64ee0949509ef3790ddaf167bf6e83d04ddff7e9ea540e10cebe710641983659cefb8ba48838dc55e82b10bbe2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## v0.4.0 (2019 Apr 16)
|
2
|
+
|
3
|
+
* REFACTOR: Introduced several DEPRECATION WARNINGs that will be harvested in v1.0.0 (@boblail)
|
4
|
+
* REFACTOR: Changed the implementation of the private method `selects` (@boblail)
|
5
|
+
|
1
6
|
## v0.3.0 (2019 Apr 7)
|
2
7
|
|
3
8
|
* IMPROVEMENT: Add support for MySQL (@boblail)
|
data/README.md
CHANGED
@@ -3,28 +3,30 @@
|
|
3
3
|
[](https://rubygems.org/gems/pluck_map)
|
4
4
|
[](https://travis-ci.org/boblail/pluck_map)
|
5
5
|
|
6
|
-
|
6
|
+
This library provides a DSL for presenting ActiveRecord::Relations without instantiating ActiveRecord models. It is useful when a Rails controller action does little more than fetch several records from the database and present them in some other data format (like JSON or CSV).
|
7
7
|
|
8
|
-
|
8
|
+
Suppose you have an action like this:
|
9
9
|
|
10
10
|
```ruby
|
11
11
|
def index
|
12
|
-
|
13
|
-
|
12
|
+
messages = Message.created_by(current_user).after(3.weeks.ago)
|
13
|
+
|
14
|
+
render json: messages.map { |message|
|
14
15
|
{ id: message.id,
|
15
16
|
postedAt: message.created_at,
|
16
17
|
text: message.text } }
|
17
18
|
end
|
18
19
|
```
|
19
20
|
|
20
|
-
This
|
21
|
+
This instantiates a `Message` for every result, gets the attributes out of it, and then immediately discards it.
|
21
22
|
|
22
|
-
We can skip that
|
23
|
+
We can skip that unnecessary instantiation by using `pluck`:
|
23
24
|
|
24
25
|
```ruby
|
25
26
|
def index
|
26
|
-
|
27
|
-
|
27
|
+
messages = Message.created_by(current_user).after(3.weeks.ago)
|
28
|
+
|
29
|
+
render json: messages.pluck(:id, :created_at, :text)
|
28
30
|
.map { |id, created, text|
|
29
31
|
{ id: id,
|
30
32
|
postedAt: created_at,
|
@@ -32,15 +34,15 @@ We can skip that step by using `pluck`:
|
|
32
34
|
end
|
33
35
|
```
|
34
36
|
|
35
|
-
|
37
|
+
This tends to be about one order of magnitude faster the first example.
|
36
38
|
|
37
|
-
|
39
|
+
It is straightforward but verbose (we repeat the attribute names at least three times) and changing a block like this produces noisy diffs:
|
38
40
|
|
39
41
|
```diff
|
40
42
|
def index
|
41
|
-
|
42
|
-
- render json:
|
43
|
-
+ render json:
|
43
|
+
messages = Message.created_by(current_user).after(3.weeks.ago)
|
44
|
+
- render json: messages.pluck(:id, :created_at, :text)
|
45
|
+
+ render json: messages.pluck(:id, :created_at, :text, :channel)
|
44
46
|
- .map { |id, created, text|
|
45
47
|
+ .map { |id, created, text, channel|
|
46
48
|
{ id: id,
|
@@ -51,28 +53,30 @@ But, now, if we needed to present a new attribute (say, `channel`), we have to w
|
|
51
53
|
end
|
52
54
|
```
|
53
55
|
|
54
|
-
|
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!
|
55
57
|
|
56
|
-
The `PluckMap::Presenter`
|
58
|
+
The `PluckMap::Presenter` is simply a shorthand for generating the above pluck-map pattern. Using it, we could write our example like this:
|
57
59
|
|
58
60
|
```ruby
|
59
61
|
def index
|
60
|
-
|
62
|
+
messages = Message.created_by(current_user).after(3.weeks.ago)
|
61
63
|
presenter = PluckMap::Presenter.new do |q|
|
62
64
|
q.id
|
63
65
|
q.postedAt select: :created_at
|
64
66
|
q.text
|
65
67
|
q.channel
|
66
68
|
end
|
67
|
-
render json: presenter.to_h
|
69
|
+
render json: presenter.to_h(messages)
|
68
70
|
end
|
69
71
|
```
|
70
72
|
|
73
|
+
Using that definition, `PluckMap::Presenter` dynamically generates a `.to_h` method that is implemented exactly like the example above that uses `.pluck` and `.map`.
|
74
|
+
|
71
75
|
This DSL also makes it easy to make fields optional:
|
72
76
|
|
73
77
|
```diff
|
74
78
|
def index
|
75
|
-
|
79
|
+
messages = Message.created_by(current_user).after(3.weeks.ago)
|
76
80
|
presenter = PluckMap::Presenter.new do |q|
|
77
81
|
q.id
|
78
82
|
q.postedAt select: :created_at
|
@@ -80,7 +84,7 @@ This DSL also makes it easy to make fields optional:
|
|
80
84
|
- q.channel
|
81
85
|
+ q.channel if params[:fields] =~ /channel/
|
82
86
|
end
|
83
|
-
render json: presenter.to_h
|
87
|
+
render json: presenter.to_h(messages)
|
84
88
|
end
|
85
89
|
```
|
86
90
|
|
@@ -104,10 +108,11 @@ Or install it yourself as:
|
|
104
108
|
|
105
109
|
## Development
|
106
110
|
|
107
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake
|
111
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
108
112
|
|
109
113
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
110
114
|
|
115
|
+
|
111
116
|
## Contributing
|
112
117
|
|
113
118
|
Bug reports and pull requests are welcome on GitHub at https://github.com/boblail/pluck_map.
|
data/Rakefile
CHANGED
data/lib/pluck_map.rb
CHANGED
data/lib/pluck_map/attribute.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module PluckMap
|
2
2
|
class Attribute
|
3
|
-
attr_reader :id, :selects, :name, :
|
3
|
+
attr_reader :id, :selects, :name, :value, :block
|
4
|
+
attr_accessor :indexes
|
4
5
|
|
5
6
|
def initialize(id, options={})
|
6
7
|
@id = id
|
7
8
|
@selects = Array(options.fetch(:select, id))
|
8
9
|
@name = options.fetch(:as, id)
|
9
|
-
@alias = name.to_s.tr("_", " ")
|
10
10
|
@block = options[:map]
|
11
11
|
|
12
12
|
if options.key? :value
|
@@ -23,17 +23,6 @@ module PluckMap
|
|
23
23
|
block.call(*object)
|
24
24
|
end
|
25
25
|
|
26
|
-
# These are the names of the values that are returned
|
27
|
-
# from the database (every row returned by the database
|
28
|
-
# will be a hash of key-value pairs)
|
29
|
-
#
|
30
|
-
# If we are only selecting one thing from the database
|
31
|
-
# then the PluckMapPresenter will automatically alias
|
32
|
-
# the select-expression, so the key will be the alias.
|
33
|
-
def keys
|
34
|
-
selects.length == 1 ? [self.alias] : selects
|
35
|
-
end
|
36
|
-
|
37
26
|
def no_map?
|
38
27
|
block.nil?
|
39
28
|
end
|
@@ -45,17 +34,37 @@ module PluckMap
|
|
45
34
|
# This method constructs a Ruby expression that will
|
46
35
|
# extract the appropriate values from each row that
|
47
36
|
# correspond to this Attribute.
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
37
|
+
def to_ruby(selects = nil)
|
38
|
+
if selects
|
39
|
+
puts "DEPRECATION WARNING: PluckMap::Attribute#to_ruby no longer requires an argument. Replace `attribute.to_ruby(keys)` with `attribute.to_ruby`."
|
40
|
+
end
|
41
|
+
|
53
42
|
return @value.inspect if defined?(@value)
|
54
|
-
indexes = self.keys.map { |key| keys.index(key) }
|
55
43
|
return "values[#{indexes[0]}]" if indexes.length == 1 && !block
|
56
44
|
ruby = "values.values_at(#{indexes.join(", ")})"
|
57
45
|
ruby = "invoke(:\"#{id}\", #{ruby})" if block
|
58
46
|
ruby
|
59
47
|
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
def values
|
52
|
+
[id, selects, name, value, block]
|
53
|
+
end
|
54
|
+
|
55
|
+
def ==(other)
|
56
|
+
return false if self.class != other.class
|
57
|
+
self.values == other.values
|
58
|
+
end
|
59
|
+
|
60
|
+
def hash
|
61
|
+
values.hash
|
62
|
+
end
|
63
|
+
|
64
|
+
def eql?(other)
|
65
|
+
return true if self.equal?(other)
|
66
|
+
return false if self.class != other.class
|
67
|
+
self.values.eql?(other.values)
|
68
|
+
end
|
60
69
|
end
|
61
70
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "pluck_map/attribute"
|
2
|
+
|
3
|
+
module PluckMap
|
4
|
+
class AttributeBuilder
|
5
|
+
|
6
|
+
def self.build(&block)
|
7
|
+
builder = self.new
|
8
|
+
if block.arity == 1
|
9
|
+
block.call(builder)
|
10
|
+
else
|
11
|
+
builder.instance_eval(&block)
|
12
|
+
end
|
13
|
+
builder.instance_variable_get(:@attributes).freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@attributes = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(attribute_name, *args)
|
21
|
+
options = args.extract_options!
|
22
|
+
options[:value] = args.first unless args.empty?
|
23
|
+
@attributes.push PluckMap::Attribute.new(attribute_name, options)
|
24
|
+
:attribute_added
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/pluck_map/presenter.rb
CHANGED
@@ -1,37 +1,33 @@
|
|
1
|
-
require "pluck_map/
|
2
|
-
require "pluck_map/
|
3
|
-
require "pluck_map/null_logger"
|
1
|
+
require "pluck_map/attribute_builder"
|
2
|
+
require "pluck_map/presenters"
|
4
3
|
|
5
4
|
module PluckMap
|
6
5
|
class Presenter
|
7
|
-
|
6
|
+
include HashPresenter
|
8
7
|
|
9
|
-
|
10
|
-
class << self
|
11
|
-
attr_accessor :logger
|
12
|
-
end
|
8
|
+
attr_reader :attributes
|
13
9
|
|
14
10
|
def initialize(&block)
|
15
|
-
@attributes =
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
20
23
|
end
|
21
|
-
@initialized = true
|
22
|
-
|
23
|
-
@attributes_by_id = attributes.index_by(&:id).with_indifferent_access
|
24
|
-
@keys = attributes.flat_map(&:keys).uniq
|
25
24
|
|
26
|
-
define_presenters
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
options[:value] = args.first unless args.empty?
|
33
|
-
attributes.push PluckMap::Attribute.new(attribute_name, options)
|
34
|
-
:attribute_added
|
25
|
+
if respond_to?(:define_presenters!, true)
|
26
|
+
puts "DEPRECATION WARNING: `define_presenters!` is deprecated; instead mix in a module that implements your presenter method (e.g. `to_h`). Optionally have the method redefine itself the first time it is called."
|
27
|
+
# because overridden `define_presenters!` will probably call `super`
|
28
|
+
PluckMap::Presenter.class_eval 'protected def define_presenters!; end'
|
29
|
+
define_presenters!
|
30
|
+
end
|
35
31
|
end
|
36
32
|
|
37
33
|
def no_map?
|
@@ -40,17 +36,9 @@ module PluckMap
|
|
40
36
|
|
41
37
|
protected
|
42
38
|
|
43
|
-
def define_presenters!
|
44
|
-
define_to_h!
|
45
|
-
end
|
46
|
-
|
47
|
-
def initialized?
|
48
|
-
@initialized
|
49
|
-
end
|
50
|
-
|
51
39
|
def pluck(query)
|
52
|
-
# puts "\e[95m#{query.select(*selects
|
53
|
-
results = benchmark("pluck(#{query.table_name})") { query.pluck(*selects
|
40
|
+
# puts "\e[95m#{query.select(*selects).to_sql}\e[0m"
|
41
|
+
results = benchmark("pluck(#{query.table_name})") { query.pluck(*selects) }
|
54
42
|
return results unless block_given?
|
55
43
|
benchmark("map(#{query.table_name})") { yield results }
|
56
44
|
end
|
@@ -58,39 +46,20 @@ module PluckMap
|
|
58
46
|
def benchmark(title)
|
59
47
|
result = nil
|
60
48
|
ms = Benchmark.ms { result = yield }
|
61
|
-
|
49
|
+
PluckMap.logger.info "\e[33m#{title}: \e[1m%.1fms\e[0m" % ms
|
62
50
|
result
|
63
51
|
end
|
64
52
|
|
65
53
|
private
|
66
|
-
attr_reader :attributes_by_id, :
|
67
|
-
|
68
|
-
def selects(model)
|
69
|
-
attributes.flat_map do |attribute|
|
70
|
-
if attribute.selects.length != 1
|
71
|
-
attribute.selects
|
72
|
-
else
|
73
|
-
select = attribute.selects[0]
|
74
|
-
select = "#{model.quoted_table_name}.#{model.connection.quote_column_name(select)}" if select.is_a?(Symbol)
|
75
|
-
Arel.sql("#{select} AS \"#{attribute.alias}\"")
|
76
|
-
end
|
77
|
-
end.uniq
|
78
|
-
end
|
54
|
+
attr_reader :attributes_by_id, :selects
|
79
55
|
|
80
56
|
def invoke(attribute_id, object)
|
81
57
|
attributes_by_id.fetch(attribute_id).apply(object)
|
82
58
|
end
|
83
59
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
pluck(query) do |results|
|
88
|
-
results.map { |values| values = Array(values); { #{attributes.map { |attribute| "#{attribute.name.inspect} => #{attribute.to_ruby(keys)}"}.join(", ")} } }
|
89
|
-
end
|
90
|
-
end
|
91
|
-
RUBY
|
92
|
-
# puts "\e[34m#{ruby}\e[0m" # <-- helps debugging PluckMapPresenter
|
93
|
-
class_eval ruby, __FILE__, __LINE__ - 7
|
60
|
+
def keys
|
61
|
+
puts "DEPRECATION WARNING: PluckMap::Presenter#keys is deprecated with no replacement"
|
62
|
+
selects
|
94
63
|
end
|
95
64
|
|
96
65
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "pluck_map/presenters/to_h"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module PluckMap
|
2
|
+
module HashPresenter
|
3
|
+
|
4
|
+
def to_h(query)
|
5
|
+
define_to_h!
|
6
|
+
to_h(query)
|
7
|
+
end
|
8
|
+
|
9
|
+
private def define_to_h!
|
10
|
+
ruby = <<-RUBY
|
11
|
+
def to_h(query)
|
12
|
+
pluck(query) do |results|
|
13
|
+
results.map { |values| values = Array(values); { #{attributes.map { |attribute| "#{attribute.name.inspect} => #{attribute.to_ruby}" }.join(", ")} } }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
RUBY
|
17
|
+
# puts "\e[34m#{ruby}\e[0m" # <-- helps debugging PluckMapPresenter
|
18
|
+
class_eval ruby, __FILE__, __LINE__ - 7
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
data/lib/pluck_map/version.rb
CHANGED
data/pluck_map.gemspec
CHANGED
@@ -29,5 +29,6 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_development_dependency "pg"
|
30
30
|
spec.add_development_dependency "database_cleaner"
|
31
31
|
spec.add_development_dependency "shoulda-context"
|
32
|
+
spec.add_development_dependency "rr"
|
32
33
|
spec.add_development_dependency "pry"
|
33
34
|
end
|
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.4.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-
|
11
|
+
date: 2019-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rr
|
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'
|
167
181
|
- !ruby/object:Gem::Dependency
|
168
182
|
name: pry
|
169
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -202,8 +216,11 @@ files:
|
|
202
216
|
- gemfiles/rails_edge.gemfile
|
203
217
|
- lib/pluck_map.rb
|
204
218
|
- lib/pluck_map/attribute.rb
|
219
|
+
- lib/pluck_map/attribute_builder.rb
|
205
220
|
- lib/pluck_map/null_logger.rb
|
206
221
|
- lib/pluck_map/presenter.rb
|
222
|
+
- lib/pluck_map/presenters.rb
|
223
|
+
- lib/pluck_map/presenters/to_h.rb
|
207
224
|
- lib/pluck_map/version.rb
|
208
225
|
- pluck_map.gemspec
|
209
226
|
homepage: https://github.com/boblail/pluck_map
|