pluckers 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +51 -0
- data/Appraisals +22 -0
- data/CHANGELOG +7 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +674 -0
- data/README.md +40 -0
- data/Rakefile +10 -0
- data/circle.yml +15 -0
- data/doc/idea.md +49 -0
- data/doc/usage/basics.md +46 -0
- data/doc/usage/extending.md +109 -0
- data/doc/usage/globalize.md +54 -0
- data/doc/usage/relationships.md +216 -0
- data/doc/usage/renaming.md +26 -0
- data/lib/pluckers/base.rb +166 -0
- data/lib/pluckers/features/active_record_3_2/belongs_to_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_3_2/globalize.rb +11 -0
- data/lib/pluckers/features/active_record_3_2/has_and_belongs_to_many_reflections.rb +40 -0
- data/lib/pluckers/features/active_record_3_2/has_many_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_3_2/has_many_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_3_2/has_one_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_3_2/has_one_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_3_2/pluck.rb +26 -0
- data/lib/pluckers/features/active_record_3_2/renaming.rb +11 -0
- data/lib/pluckers/features/active_record_3_2/simple_attributes.rb +11 -0
- data/lib/pluckers/features/active_record_3_2.rb +10 -0
- data/lib/pluckers/features/active_record_4_0/belongs_to_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_4_0/globalize.rb +11 -0
- data/lib/pluckers/features/active_record_4_0/has_and_belongs_to_many_reflections.rb +40 -0
- data/lib/pluckers/features/active_record_4_0/has_many_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_4_0/has_many_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_0/has_one_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_0/has_one_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_0/pluck.rb +11 -0
- data/lib/pluckers/features/active_record_4_0/renaming.rb +11 -0
- data/lib/pluckers/features/active_record_4_0/simple_attributes.rb +11 -0
- data/lib/pluckers/features/active_record_4_0.rb +10 -0
- data/lib/pluckers/features/active_record_4_1/belongs_to_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_4_1/globalize.rb +11 -0
- data/lib/pluckers/features/active_record_4_1/has_and_belongs_to_many_reflections.rb +40 -0
- data/lib/pluckers/features/active_record_4_1/has_many_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_4_1/has_many_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_1/has_one_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_1/has_one_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_1/pluck.rb +11 -0
- data/lib/pluckers/features/active_record_4_1/renaming.rb +11 -0
- data/lib/pluckers/features/active_record_4_1/simple_attributes.rb +11 -0
- data/lib/pluckers/features/active_record_4_1.rb +10 -0
- data/lib/pluckers/features/active_record_4_2/belongs_to_reflections.rb +15 -0
- data/lib/pluckers/features/active_record_4_2/globalize.rb +11 -0
- data/lib/pluckers/features/active_record_4_2/has_and_belongs_to_many_reflections.rb +39 -0
- data/lib/pluckers/features/active_record_4_2/has_many_reflections.rb +15 -0
- data/lib/pluckers/features/active_record_4_2/has_many_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_2/has_one_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_4_2/has_one_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_4_2/pluck.rb +11 -0
- data/lib/pluckers/features/active_record_4_2/renaming.rb +11 -0
- data/lib/pluckers/features/active_record_4_2/simple_attributes.rb +11 -0
- data/lib/pluckers/features/active_record_4_2.rb +10 -0
- data/lib/pluckers/features/active_record_5_0/belongs_to_reflections.rb +15 -0
- data/lib/pluckers/features/active_record_5_0/globalize.rb +11 -0
- data/lib/pluckers/features/active_record_5_0/has_and_belongs_to_many_reflections.rb +39 -0
- data/lib/pluckers/features/active_record_5_0/has_many_reflections.rb +15 -0
- data/lib/pluckers/features/active_record_5_0/has_many_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_5_0/has_one_reflections.rb +16 -0
- data/lib/pluckers/features/active_record_5_0/has_one_through_reflections.rb +17 -0
- data/lib/pluckers/features/active_record_5_0/pluck.rb +11 -0
- data/lib/pluckers/features/active_record_5_0/renaming.rb +11 -0
- data/lib/pluckers/features/active_record_5_0/simple_attributes.rb +11 -0
- data/lib/pluckers/features/active_record_5_0.rb +10 -0
- data/lib/pluckers/features/base/belongs_to_reflections.rb +131 -0
- data/lib/pluckers/features/base/globalize.rb +116 -0
- data/lib/pluckers/features/base/has_and_belongs_to_many_reflections.rb +190 -0
- data/lib/pluckers/features/base/has_many_reflections.rb +193 -0
- data/lib/pluckers/features/base/has_many_through_reflections.rb +131 -0
- data/lib/pluckers/features/base/has_one_reflections.rb +122 -0
- data/lib/pluckers/features/base/has_one_through_reflections.rb +129 -0
- data/lib/pluckers/features/base/pluck.rb +30 -0
- data/lib/pluckers/features/base/renaming.rb +55 -0
- data/lib/pluckers/features/base/simple_attributes.rb +64 -0
- data/lib/pluckers/version.rb +3 -0
- data/lib/pluckers.rb +7 -0
- data/pluckers.gemspec +38 -0
- metadata +236 -0
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Pluckers
|
2
|
+
|
3
|
+
[![CircleCI](https://circleci.com/gh/simplelogica/pluckers/tree/master.svg?style=svg)](https://circleci.com/gh/simplelogica/pluckers/tree/master)
|
4
|
+
|
5
|
+
This gem extends the idea behind AR's pluck method so we can fetch data from multiple tables and create our own classes to encapsulate how we fetch data from the database and which bussines logic may be applied to them. You can read more about [The Idea](./doc/idea.md).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'pluckers'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install pluckers
|
22
|
+
|
23
|
+
## USAGE
|
24
|
+
|
25
|
+
In this section you will learn
|
26
|
+
|
27
|
+
* [How to use a plucker and what do you obtain from it](./doc/usage/basics.md)
|
28
|
+
* [How to use your plucker with your globalized methods](./doc/usage/globalize.md)
|
29
|
+
* [How to rename your fetched attributes](./doc/usage/renaming.md)
|
30
|
+
* [How to use your plucker for traversing through relationships in a recursive way and obtain data from several tables without N+1 and with the minimum queries](./doc/usage/relationships.md)
|
31
|
+
* [How to create your own plucker classes to encapsulate all your plucking options and logic](./doc/usage/extending.md)
|
32
|
+
|
33
|
+
## Development
|
34
|
+
|
35
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
36
|
+
|
37
|
+
## Contributing
|
38
|
+
|
39
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/simplelogica/pluckers. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
40
|
+
|
data/Rakefile
ADDED
data/circle.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
dependencies:
|
2
|
+
pre:
|
3
|
+
- gem uninstall bundler -x
|
4
|
+
- gem install bundler
|
5
|
+
post:
|
6
|
+
- bundle exec appraisal install
|
7
|
+
test:
|
8
|
+
override:
|
9
|
+
- bundle exec appraisal rake test
|
10
|
+
database:
|
11
|
+
override:
|
12
|
+
- bundle exec rake -T
|
13
|
+
machine:
|
14
|
+
ruby:
|
15
|
+
version: "2.3"
|
data/doc/idea.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
## The idea
|
2
|
+
|
3
|
+
ActiveRecord is a powerful tool to create and mantain the model and persistence of a Ruby application, but just as every tool it must be used when it's really needed.
|
4
|
+
|
5
|
+
As an example, ActiveRecord objects are quite expensive to instantiate compared with simpler objects, such as arrays or hashes. In the following benchmark we can see times returned by Ruby's benchmark for some simple samples.
|
6
|
+
|
7
|
+
```
|
8
|
+
> puts Benchmark.measure { 1000.times{BlogPost.new} }
|
9
|
+
0.030000 0.010000 0.040000 ( 0.079227)
|
10
|
+
=> nil
|
11
|
+
> puts Benchmark.measure { 10000.times{BlogPost.new} }
|
12
|
+
0.320000 0.000000 0.320000 ( 0.381990)
|
13
|
+
=> nil
|
14
|
+
> puts Benchmark.measure { 100000.times{BlogPost.new} }
|
15
|
+
3.350000 0.010000 3.360000 ( 3.546527)
|
16
|
+
=> nil
|
17
|
+
```
|
18
|
+
|
19
|
+
```
|
20
|
+
> puts Benchmark.measure { 1000.times{Hash.new} }
|
21
|
+
0.010000 0.000000 0.010000 ( 0.001668)
|
22
|
+
=> nil
|
23
|
+
> puts Benchmark.measure { 10000.times{Hash.new} }
|
24
|
+
0.020000 0.000000 0.020000 ( 0.039580)
|
25
|
+
=> nil
|
26
|
+
> puts Benchmark.measure { 100000.times{Hash.new} }
|
27
|
+
0.140000 0.020000 0.160000 ( 0.228560)
|
28
|
+
=> nil
|
29
|
+
```
|
30
|
+
|
31
|
+
This is the idea behind the `pluck` method ActiveRecord includes. Use ActiveRecord to manage the persistence and connection to the database, but return simple objects such as Arrays or Hashes to manage the information in ruby, avoiding to instantiate heavier objects.
|
32
|
+
|
33
|
+
```
|
34
|
+
> 10000.times {|i| BlogPost.create(title: "Title #{i}")}
|
35
|
+
|
36
|
+
> puts Benchmark.measure { BlogPost.all.map(&:title) }
|
37
|
+
0.560000 0.010000 0.570000 ( 0.704659)
|
38
|
+
=> nil
|
39
|
+
|
40
|
+
> puts Benchmark.measure { BlogPost.pluck(:title) }
|
41
|
+
0.110000 0.000000 0.110000 ( 0.172678)
|
42
|
+
=> nil
|
43
|
+
```
|
44
|
+
|
45
|
+
Unfortunately, `pluck` method is limited to attributes from the model and lack some other features, such as navigate through relations.
|
46
|
+
|
47
|
+
This gem `pluckers` creates a new kind of objects (yes, Pluckers) that encapsulate all the logic of plucking attributes and relations in a recursive way just using the definition of our model created by ActiveRecord but instantiating just arrays and hashes, not a single ActiveRecord::Base object.
|
48
|
+
|
49
|
+
Furthermore, these objects will become a single point of access to the database that will force us to think which information we really need, avoiding long SELECT queries and ending with N+1 issues in a clean and transparent way.
|
data/doc/usage/basics.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Creating the plucker and fetching basic data
|
2
|
+
|
3
|
+
## Creating the plucker
|
4
|
+
|
5
|
+
You may use the `Pluckers::Base` class to pluck all the information you need:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
Pluckers::Base.new(Model.scope, options)
|
9
|
+
```
|
10
|
+
|
11
|
+
You can use any ActiveRecord Relation. It means you can pluck any scope or collection just as you would use them in your Rails applications:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
plucker = Pluckers::Base.new(BlogPost.published)
|
15
|
+
plucker = Pluckers::Base.new(Author.all)
|
16
|
+
plucker = Pluckers::Base.new(post.categories.published)
|
17
|
+
```
|
18
|
+
|
19
|
+
Once you have the plucker object you just... pluck.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
plucker.pluck
|
23
|
+
```
|
24
|
+
|
25
|
+
If you don't select any columns the plucker will use the `attributes` method of the class to pluck all the attributes from the database.
|
26
|
+
|
27
|
+
## Selecting columns
|
28
|
+
|
29
|
+
When you create the plucker you can configure some options to customize it.
|
30
|
+
|
31
|
+
First, you can choose which columns to pluck from the table, so you don't pluck 50 columns when you only need three of them. To do so you will use the `attributes` option.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
Pluckers::Base.new(BlogPost.published, { attributes: [:title, :slug, :published_at] }).pluck
|
35
|
+
```
|
36
|
+
```ruby
|
37
|
+
[
|
38
|
+
{ id: 33, title: "Lorem Ipsum", slug: 'lorem-ipsum', published_at: "2016-04-07"},
|
39
|
+
{ id: 34, title: "Lorem Ipsum 3", slug: 'lorem-ipsum-3', published_at: "2016-04-09"},
|
40
|
+
{ id: 35, title: "Lorem Ipsum 4", slug: 'lorem-ipsum-4', published_at: "2016-04-12"}
|
41
|
+
]
|
42
|
+
```
|
43
|
+
|
44
|
+
Of course, this will be done in just one query.
|
45
|
+
|
46
|
+
NEXT: [How to use your plucker with your globalized methods](./globalize.md)
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# Extending pluckers
|
2
|
+
|
3
|
+
PREVIOUSLY: [How to use your plucker for traversing through relationships and obtain data from several tables without N+1 and with the minimum queries](./relationships.md)
|
4
|
+
|
5
|
+
In some cases you may have a plucker configuration you use in several places in your application, or complex enough to extract it from the controller and isolate it so it can be easily debugged or even tested in some unit test.
|
6
|
+
|
7
|
+
You can achieve this by creating your own plucker classes that initialize the right options and delegate the complex stuff to its base class.
|
8
|
+
|
9
|
+
## Initializing options
|
10
|
+
|
11
|
+
As an example, imagine we want a plucker so we can show in a menu all the categories of our blog. We could use the base plucker as before:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
Pluckers::Base.new(Category.published, {
|
15
|
+
attributes: [:name],
|
16
|
+
attributes_with_locale: { es: [:name] }
|
17
|
+
}).pluck
|
18
|
+
```
|
19
|
+
|
20
|
+
Or we could have a new class:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class CategoryMenuPlucker < Pluckers::Base
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
|
27
|
+
super(Category.published, {
|
28
|
+
attributes: [:name],
|
29
|
+
attributes_with_locale: { es: [:name] }
|
30
|
+
})
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
And just a simple pluck call in our controller:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
CategoryMenuPlucker.new.pluck
|
40
|
+
```
|
41
|
+
|
42
|
+
We could even allow an options argument in the new class so we can customize it in different invocations.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
|
46
|
+
class CategoryMenuPlucker < Pluckers::Base
|
47
|
+
|
48
|
+
def initialize options = {}
|
49
|
+
|
50
|
+
options[:attributes] ||= []
|
51
|
+
options[:attributes] << :name
|
52
|
+
|
53
|
+
options[:attributes_with_locale] ||= {}
|
54
|
+
options[:attributes_with_locale][:es] ||= []
|
55
|
+
options[:attributes_with_locale][:es] << :name
|
56
|
+
|
57
|
+
|
58
|
+
super(Category.published, options)
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
CategoryMenuPlucker.new.pluck
|
66
|
+
CategoryMenuPlucker.new({renames: { name_es: :name_for_analytics}}).pluck
|
67
|
+
```
|
68
|
+
|
69
|
+
## Customizing the plucked results
|
70
|
+
|
71
|
+
In this extended plucker we could even add some logic after all the results are plucked. Imagine we only want those categories with published posts. We could do the following:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
|
75
|
+
class CategoryMenuPlucker < Pluckers::Base
|
76
|
+
|
77
|
+
def initialize options = {}
|
78
|
+
|
79
|
+
options[:attributes] ||= []
|
80
|
+
options[:attributes] << :name
|
81
|
+
|
82
|
+
options[:attributes_with_locale] ||= {}
|
83
|
+
options[:attributes_with_locale][:es] ||= []
|
84
|
+
options[:attributes_with_locale][:es] << :name
|
85
|
+
|
86
|
+
|
87
|
+
options[:reflections] ||= {}
|
88
|
+
options[:reflections][:posts] = {
|
89
|
+
attributes: [:id],
|
90
|
+
}
|
91
|
+
|
92
|
+
super(Category.published, options)
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
def pluck
|
97
|
+
results = super
|
98
|
+
|
99
|
+
results.select do |category|
|
100
|
+
category[:posts].count > 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
This way, when we execute `CategoryMenuPlucker.new.pluck` we are plucking the categories and discarding those that don't meet our expectations.
|
108
|
+
|
109
|
+
And more important, this logic is encapsulated in the class responsible for fetching the data from the database, isolated from the controller and easily testeable.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Selecting Globalized columns
|
2
|
+
|
3
|
+
PREVIOUSLY: [How to use a plucker and what do you obtain from it](./basics.md)
|
4
|
+
|
5
|
+
If you are using Globalize you may find useful to pluck translated columns. You just have to include it in the attributes options and it will automatically recognize it as a translated column and will pluck it from that table.
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
Pluckers::Base.new(post.categories.published, { attributes: [:name] }).pluck
|
9
|
+
```
|
10
|
+
```ruby
|
11
|
+
[
|
12
|
+
{ id: 2, name: "gifs" },
|
13
|
+
{ id: 34, name: "shiba" },
|
14
|
+
{ id: 35, name: "ducktales" }
|
15
|
+
]
|
16
|
+
```
|
17
|
+
## Selecting a specific locale
|
18
|
+
|
19
|
+
In some scenarios you may need to pluck some specific language. You can do it with the `attributes_with_locale` options.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
Pluckers::Base.new(post.categories.published, { attributes_with_locale: { es: [:name] }).pluck
|
23
|
+
```
|
24
|
+
```ruby
|
25
|
+
[
|
26
|
+
{ id: 2, name_es: "gifs" },
|
27
|
+
{ id: 34, name_es: "shiba" },
|
28
|
+
{ id: 35, name_es: "patoaventuras" }
|
29
|
+
]
|
30
|
+
```
|
31
|
+
|
32
|
+
Since these are independent options you can combine them.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
Pluckers::Base.new(post.categories.published, {
|
36
|
+
attributes: [:name],
|
37
|
+
attributes_with_locale: { es: [:name] }
|
38
|
+
}).pluck
|
39
|
+
```
|
40
|
+
```ruby
|
41
|
+
[
|
42
|
+
{ id: 2, name: "gifs", name_es: "gifs" },
|
43
|
+
{ id: 34, name: "shiba", name_es: "shiba" },
|
44
|
+
{ id: 35, name: "ducktales", name_es: "patoaventuras" }
|
45
|
+
]
|
46
|
+
```
|
47
|
+
|
48
|
+
## Fallback translations
|
49
|
+
|
50
|
+
Pluckers will use Globalize fallback locales configuration to return the most appropiate value. I.e. If some post has no content on english locale and its fallback is spanish, it will return the value in spanish locale.
|
51
|
+
|
52
|
+
All these operations will be done in an extra query, no matter the number of locales available in Globalize.
|
53
|
+
|
54
|
+
NEXT: [How to rename your fetched attributes](./renaming.md)
|
@@ -0,0 +1,216 @@
|
|
1
|
+
# Traversing relationships
|
2
|
+
|
3
|
+
PREVIOUSLY: [How to rename your fetched attributes](./renaming.md)
|
4
|
+
|
5
|
+
Until now you can pluck attributes. Now we introduce an option to traverse relationships in the model, so you can pluck not only one model, but any related model, through the `reflections` option.
|
6
|
+
|
7
|
+
## Fetching relationships
|
8
|
+
|
9
|
+
Imagine, for the previous example, you want to pluck the post information for each category.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Pluckers::Base.new(post.categories.published, {
|
13
|
+
attributes: [:name],
|
14
|
+
attributes_with_locale: { es: [:name] },
|
15
|
+
renames: { name_es: :name_for_analytics},
|
16
|
+
reflections: {
|
17
|
+
posts: {
|
18
|
+
attributes: [:title, :slug, :published_at],
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}).pluck
|
22
|
+
```
|
23
|
+
```ruby
|
24
|
+
[
|
25
|
+
{ id: 2, name: "gifs", name_for_analytics: "gifs",
|
26
|
+
posts: [
|
27
|
+
{ id: 33, title: "Lorem Ipsum", slug: 'lorem-ipsum', published_at: "2016-04-07"},
|
28
|
+
{ id: 32, title: "Lorem Ipsum not", slug: 'lorem-ipsum-not', published_at: nil}
|
29
|
+
]
|
30
|
+
},
|
31
|
+
{ id: 34, name: "shiba", name_for_analytics: "shiba",
|
32
|
+
posts: [
|
33
|
+
{ id: 34, title: "Lorem Ipsum 3", slug: 'lorem-ipsum-3', published_at: "2016-04-09"},
|
34
|
+
{ id: 35, title: "Lorem Ipsum 4", slug: 'lorem-ipsum-4', published_at: "2016-04-12"}
|
35
|
+
]
|
36
|
+
},
|
37
|
+
{ id: 35, name: "ducktales", name_for_analytics: "patoaventuras"
|
38
|
+
posts: [
|
39
|
+
{ id: 33, title: "Lorem Ipsum", slug: 'lorem-ipsum', published_at: "2016-04-07"}
|
40
|
+
{ id: 34, title: "Lorem Ipsum 3", slug: 'lorem-ipsum-3', published_at: "2016-04-09"},
|
41
|
+
{ id: 35, title: "Lorem Ipsum 4", slug: 'lorem-ipsum-4', published_at: "2016-04-12"}
|
42
|
+
]
|
43
|
+
}
|
44
|
+
]
|
45
|
+
```
|
46
|
+
|
47
|
+
As we just use the `attributes` option for the reflection we just perform an extra database query, avoiding N+1.
|
48
|
+
|
49
|
+
Each element in the reflections options has a key and a hash of options. The key is the name of the relationship as defined in the Active Record model. The value is a hash of options that takes the exact same options that are allowed for the plucker. In fact, internally we create another plucker to retrieve the posts.
|
50
|
+
|
51
|
+
This means that we can do everything in this "secondary" plucker. We can get globalize columns, we can rename... and we can also get another related models, giving us the ability to obtain a whole tree of models and objects in just one single point, with the minimum database queries required.
|
52
|
+
|
53
|
+
## Foreign keys and minimum data plucked
|
54
|
+
|
55
|
+
Although in the examples we only show ids involved, relationships configured with different foreign keys can be fetched too as the configuration is read by the plucker to use the proper columns in both involved tables.
|
56
|
+
|
57
|
+
In order to be able to relate the plucked data all the foreign keys must be plucked, that's why in the previous example all the posts plucked their `id` although in the `attributes` option we specied only `[:title, :slug, :published_at]`.
|
58
|
+
|
59
|
+
## Fetching only ids
|
60
|
+
|
61
|
+
For `has_many` and `has_and_belongs_to_many` relationships you could be interested in only fetching the ids of the related objects. You can get this by using the `only_ids` option in the relationship to fetch.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
Pluckers::Base.new(post.categories.published, {
|
65
|
+
attributes: [:name],
|
66
|
+
attributes_with_locale: { es: [:name] },
|
67
|
+
renames: { name_es: :name_for_analytics},
|
68
|
+
reflections: {
|
69
|
+
posts: { only_ids: true }
|
70
|
+
}
|
71
|
+
}).pluck
|
72
|
+
```
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
[
|
76
|
+
{ id: 2, name: "gifs", name_for_analytics: "gifs",
|
77
|
+
post_ids: [33, 32]
|
78
|
+
},
|
79
|
+
{ id: 34, name: "shiba", name_for_analytics: "shiba",
|
80
|
+
posts_ids: [34, 35]
|
81
|
+
},
|
82
|
+
{ id: 35, name: "ducktales", name_for_analytics: "patoaventuras"
|
83
|
+
post_ids: [33, 34, 35]
|
84
|
+
}
|
85
|
+
]
|
86
|
+
```
|
87
|
+
|
88
|
+
## Traversing relationships in a recursive way
|
89
|
+
|
90
|
+
As an example, imagine we want to obtain the name of the author of each one of the retrieved post.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
Pluckers::Base.new(post.categories.published, {
|
94
|
+
attributes: [:name],
|
95
|
+
attributes_with_locale: { es: [:name] },
|
96
|
+
renames: { name_es: :name_for_analytics},
|
97
|
+
reflections: {
|
98
|
+
posts: {
|
99
|
+
attributes: [:title, :slug, :published_at],
|
100
|
+
reflections: {
|
101
|
+
author: { attributes: [:name] }
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}).pluck
|
106
|
+
```
|
107
|
+
```ruby
|
108
|
+
[
|
109
|
+
{ id: 2, name: "gifs", name_for_analytics: "gifs",
|
110
|
+
posts: [
|
111
|
+
{ id: 33, title: "Lorem Ipsum", slug: 'lorem-ipsum', published_at: "2016-04-07",
|
112
|
+
autor: { id: 1, name: "Someone"}
|
113
|
+
},
|
114
|
+
{ id: 32, title: "Lorem Ipsum not", slug: 'lorem-ipsum-not', published_at: nil,
|
115
|
+
autor: { id: 2, name: "Someone else"}
|
116
|
+
}
|
117
|
+
]
|
118
|
+
},
|
119
|
+
{ id: 34, name: "shiba", name_for_analytics: "shiba",
|
120
|
+
posts: [
|
121
|
+
{ id: 34, title: "Lorem Ipsum 3", slug: 'lorem-ipsum-3', published_at: "2016-04-09",
|
122
|
+
autor: { id: 3, name: "Another one"}
|
123
|
+
},
|
124
|
+
{ id: 35, title: "Lorem Ipsum 4", slug: 'lorem-ipsum-4', published_at: "2016-04-12",
|
125
|
+
autor: { id: 1, name: "Someone"}
|
126
|
+
}
|
127
|
+
]
|
128
|
+
},
|
129
|
+
{ id: 35, name: "ducktales", name_for_analytics: "patoaventuras"
|
130
|
+
posts: [
|
131
|
+
{ id: 33, title: "Lorem Ipsum", slug: 'lorem-ipsum', published_at: "2016-04-07",
|
132
|
+
autor: { id: 1, name: "Someone"}
|
133
|
+
},
|
134
|
+
{ id: 34, title: "Lorem Ipsum 3", slug: 'lorem-ipsum-3', published_at: "2016-04-09",
|
135
|
+
autor: { id: 3, name: "Another one"}
|
136
|
+
},
|
137
|
+
{ id: 35, title: "Lorem Ipsum 4", slug: 'lorem-ipsum-4', published_at: "2016-04-12",
|
138
|
+
autor: { id: 1, name: "Someone"}
|
139
|
+
}
|
140
|
+
]
|
141
|
+
}
|
142
|
+
]
|
143
|
+
```
|
144
|
+
|
145
|
+
This would've been performed with 4 database queries:
|
146
|
+
|
147
|
+
- Category attributes.
|
148
|
+
- Category globalized attributes.
|
149
|
+
- Related posts attributes.
|
150
|
+
- Related authors from related posts attributes.
|
151
|
+
|
152
|
+
## Applying scopes
|
153
|
+
|
154
|
+
Sometimes may be useful to apply some restrictions on the related objects we are plucking. Maybe we don't want all the posts to be plucked, only the published ones in order to show the links.
|
155
|
+
|
156
|
+
We can restrict the related objects to be plucked through the `scope` option which accepts standard ActiveRecord scopes which will be used to build the query when plucking related objects.
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
Pluckers::Base.new(post.categories.published, {
|
160
|
+
attributes: [:name],
|
161
|
+
attributes_with_locale: { es: [:name] },
|
162
|
+
renames: { name_es: :name_for_analytics},
|
163
|
+
reflections: {
|
164
|
+
posts: {
|
165
|
+
attributes: [:title, :slug, :published_at],
|
166
|
+
reflections: {
|
167
|
+
author: { attributes: [:name] }
|
168
|
+
},
|
169
|
+
scope: BlogPost.published
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}).pluck
|
173
|
+
```
|
174
|
+
```ruby
|
175
|
+
[
|
176
|
+
{ id: 2, name: "gifs", name_for_analytics: "gifs",
|
177
|
+
posts: [
|
178
|
+
{ id: 33, title: "Lorem Ipsum", slug: 'lorem-ipsum', published_at: "2016-04-07",
|
179
|
+
autor: { id: 1, name: "Someone"}
|
180
|
+
}
|
181
|
+
]
|
182
|
+
},
|
183
|
+
{ id: 34, name: "shiba", name_for_analytics: "shiba",
|
184
|
+
posts: [
|
185
|
+
{ id: 34, title: "Lorem Ipsum 3", slug: 'lorem-ipsum-3', published_at: "2016-04-09",
|
186
|
+
autor: { id: 3, name: "Another one"}
|
187
|
+
},
|
188
|
+
{ id: 35, title: "Lorem Ipsum 4", slug: 'lorem-ipsum-4', published_at: "2016-04-12",
|
189
|
+
autor: { id: 1, name: "Someone"}
|
190
|
+
}
|
191
|
+
]
|
192
|
+
},
|
193
|
+
{ id: 35, name: "ducktales", name_for_analytics: "patoaventuras"
|
194
|
+
posts: [
|
195
|
+
{ id: 33, title: "Lorem Ipsum", slug: 'lorem-ipsum', published_at: "2016-04-07",
|
196
|
+
autor: { id: 1, name: "Someone"}
|
197
|
+
},
|
198
|
+
{ id: 34, title: "Lorem Ipsum 3", slug: 'lorem-ipsum-3', published_at: "2016-04-09",
|
199
|
+
autor: { id: 3, name: "Another one"}
|
200
|
+
},
|
201
|
+
{ id: 35, title: "Lorem Ipsum 4", slug: 'lorem-ipsum-4', published_at: "2016-04-12",
|
202
|
+
autor: { id: 1, name: "Someone"}
|
203
|
+
}
|
204
|
+
]
|
205
|
+
}
|
206
|
+
]
|
207
|
+
```
|
208
|
+
|
209
|
+
This would've been performed with 4 database queries:
|
210
|
+
|
211
|
+
- Category attributes.
|
212
|
+
- Category globalized attributes.
|
213
|
+
- Related posts attributes filtering with the `published` scope as defined in the `BlogPost` model.
|
214
|
+
- Related authors from related posts attributes.
|
215
|
+
|
216
|
+
NEXT: [How to create your own plucker classes to encapsulate all your plucking options and logic](./extending.md)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Renaming columns
|
2
|
+
|
3
|
+
PREVIOUSLY: [How to use your plucker with your globalized methods](./globalize.md)
|
4
|
+
|
5
|
+
Imagine you're plucking your spanish name to use it in Google Analytics integration so the visit is registered to the same category, no matter the language.
|
6
|
+
|
7
|
+
You may use `name_es` in your code or you could rename the attribute to a more meaningful name such as `name_for_analytics` through the `renames` option.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
Pluckers::Base.new(post.categories.published, {
|
11
|
+
attributes: [:name],
|
12
|
+
attributes_with_locale: { es: [:name] },
|
13
|
+
renames: { name_es: :name_for_analytics}
|
14
|
+
}).pluck
|
15
|
+
```
|
16
|
+
```ruby
|
17
|
+
[
|
18
|
+
{ id: 2, name: "gifs", name_for_analytics: "gifs" },
|
19
|
+
{ id: 34, name: "shiba", name_for_analytics: "shiba" },
|
20
|
+
{ id: 35, name: "ducktales", name_for_analytics: "patoaventuras" }
|
21
|
+
]
|
22
|
+
```
|
23
|
+
|
24
|
+
This will require no extra database query.
|
25
|
+
|
26
|
+
NEXT: [How to use your plucker for traversing through relationships in a recursive way and obtain data from several tables without N+1 and with the minimum queries](./relationships.md)
|