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
@@ -0,0 +1,116 @@
|
|
1
|
+
module Pluckers
|
2
|
+
##
|
3
|
+
# This module groups diferent modules that will configure and build the
|
4
|
+
# results for a specific kind of information (attributes, translations,
|
5
|
+
# relations...)
|
6
|
+
#
|
7
|
+
# All this modules will have two methods, one for configuration (e.g, attributes
|
8
|
+
# to be included in the real pluck) and one for building the final results
|
9
|
+
module Features
|
10
|
+
|
11
|
+
module Base
|
12
|
+
|
13
|
+
##
|
14
|
+
# This module implements plucking belongs_to relationships in a recursive
|
15
|
+
# way.
|
16
|
+
#
|
17
|
+
# The options used in this feature are:
|
18
|
+
#
|
19
|
+
# * attributes: Names of attributes of the objects to be plucked. This
|
20
|
+
# attributes should be the names of the translated attributes by Globalize.
|
21
|
+
#
|
22
|
+
# * attributes_with_locale: A hash when the key is a locale and the value
|
23
|
+
# is an array of attributes to pluck. As a result we will have a series of
|
24
|
+
# attributes with the name following the syntax attreibute_locale. E.g: The
|
25
|
+
# option could be { es: [:name], en: [:name, :location]} and we would obtain
|
26
|
+
# :name_es, :name_en and :location_en keys in the hash result
|
27
|
+
|
28
|
+
module Globalize
|
29
|
+
|
30
|
+
|
31
|
+
##
|
32
|
+
# Here we include in the @attributes_to_pluck array the attribute names
|
33
|
+
# and SQL required for Globalize
|
34
|
+
def configure_query
|
35
|
+
super
|
36
|
+
|
37
|
+
return if @klass_reflections[:translations].nil?
|
38
|
+
|
39
|
+
if @options[:attributes]
|
40
|
+
|
41
|
+
# First we get those attributes received bia the attributes option
|
42
|
+
# that must be translated
|
43
|
+
plucker_attributes = @options[:attributes].map(&:to_sym)
|
44
|
+
|
45
|
+
klass_translated_attributes = @records.try(:translated_attribute_names) || []
|
46
|
+
klass_translated_attributes = klass_translated_attributes.map(&:to_sym)
|
47
|
+
|
48
|
+
@translated_attributes = plucker_attributes & klass_translated_attributes
|
49
|
+
|
50
|
+
# And we remove it from the attributes options, so the simple
|
51
|
+
# attributes feature don't receive them
|
52
|
+
@options[:attributes] = plucker_attributes - klass_translated_attributes
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# And initialize with an empty array if we didn't receive any
|
57
|
+
@translated_attributes ||= []
|
58
|
+
|
59
|
+
# And then get the attributes that must be returned for a specific locale
|
60
|
+
@attributes_with_locale = @options[:attributes_with_locale] || {}
|
61
|
+
|
62
|
+
unless @translated_attributes.blank? && @attributes_with_locale.blank?
|
63
|
+
|
64
|
+
# We obtain the info about the translations relation
|
65
|
+
translation_table_name = @records.klass::Translation.table_name
|
66
|
+
translation_foreign_key = @klass_reflections[:translations].foreign_key
|
67
|
+
|
68
|
+
# And we perform a join for each locale
|
69
|
+
fallbacks = ::Globalize.fallbacks(::Globalize.locale)
|
70
|
+
|
71
|
+
fallbacks.each do |locale|
|
72
|
+
@records = @records.joins(
|
73
|
+
"LEFT OUTER JOIN #{translation_table_name} AS locale_#{locale}_translation ON (
|
74
|
+
#{@records.klass.table_name}.id = locale_#{locale}_translation.#{translation_foreign_key} AND
|
75
|
+
locale_#{locale}_translation.locale = '#{locale}'
|
76
|
+
)")
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
# The attribute to pluck must get the first non nil field
|
81
|
+
@attributes_to_pluck += @translated_attributes.map do |field|
|
82
|
+
{
|
83
|
+
name: field,
|
84
|
+
sql: "COALESCE(NULL, #{
|
85
|
+
fallbacks.map{|locale| "locale_#{locale}_translation.#{field}" }.join(',')
|
86
|
+
})"
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
# For the attributes with locale (name_es, name_en...)
|
91
|
+
@attributes_with_locale.each do |locale, attributes|
|
92
|
+
|
93
|
+
# We add the locales that are not fallback
|
94
|
+
unless fallbacks.include? locale
|
95
|
+
@records = @records.joins(
|
96
|
+
"LEFT OUTER JOIN #{translation_table_name} AS locale_#{locale}_translation ON (
|
97
|
+
#{@records.klass.table_name}.id = locale_#{locale}_translation.#{translation_foreign_key} AND
|
98
|
+
locale_#{locale}_translation.locale = '#{locale}'
|
99
|
+
)")
|
100
|
+
end
|
101
|
+
|
102
|
+
# And we add the attribute to be plucked
|
103
|
+
@attributes_to_pluck += attributes.map do |field|
|
104
|
+
{
|
105
|
+
name: "#{field}_#{locale}".to_sym,
|
106
|
+
sql: "locale_#{locale}_translation.#{field}"
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Pluckers
|
2
|
+
##
|
3
|
+
# This module groups diferent modules that will configure and build the
|
4
|
+
# results for a specific kind of information (attributes, translations,
|
5
|
+
# relations...)
|
6
|
+
#
|
7
|
+
# All this modules will have two methods, one for configuration (e.g, attributes
|
8
|
+
# to be included in the real pluck) and one for building the final results
|
9
|
+
module Features
|
10
|
+
|
11
|
+
module Base
|
12
|
+
|
13
|
+
##
|
14
|
+
# This module implements plucking has_and_belongs_to_many relationships in a recursive
|
15
|
+
# way.
|
16
|
+
#
|
17
|
+
# The options used in this feature are:
|
18
|
+
#
|
19
|
+
# * reflections: A hash of the reflections we will pluck recursively. The
|
20
|
+
# key of this hash will be the name of the reflection and the value is
|
21
|
+
# another hash of options.
|
22
|
+
#
|
23
|
+
# - scope: You can limit the scope of the objects plucked. E.g, you
|
24
|
+
# could use Author.active instead of Author.all. Notice that .all is
|
25
|
+
# the default.
|
26
|
+
#
|
27
|
+
# - plucker: You can use a custom plucker instead of Pluckers::Base in
|
28
|
+
# case you want any specific logic. Pluckers::Base is the default one.
|
29
|
+
#
|
30
|
+
# - Any other option will be passed to the plucker, so you can send any
|
31
|
+
# other regular option such as attributes, custom ones or even more
|
32
|
+
# reflections. Recursivity FTW!!
|
33
|
+
#
|
34
|
+
module HasAndBelongsToManyReflections
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
# Here we obtain the has_many reflections to include in the pluck
|
39
|
+
# operation and also include the relation foreign key in the attributes to
|
40
|
+
# pluck for this model.
|
41
|
+
def configure_query
|
42
|
+
super
|
43
|
+
|
44
|
+
pluck_reflections = @options[:reflections] || {}
|
45
|
+
|
46
|
+
return if pluck_reflections.blank?
|
47
|
+
|
48
|
+
@has_and_belongs_to_many_reflections = { }
|
49
|
+
|
50
|
+
# We iterate through the class reflections passed as options
|
51
|
+
@klass_reflections.slice(*pluck_reflections.keys).
|
52
|
+
# And select those that are HasMany
|
53
|
+
select{|_, r| active_record_has_and_belongs_to_many_reflection?(r) }.
|
54
|
+
# And store them in the has_many_reflection hash that will be used later
|
55
|
+
each do |name, reflection|
|
56
|
+
name = name.to_sym
|
57
|
+
@has_and_belongs_to_many_reflections[name] = pluck_reflections[name]
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# In this method we get the reflections and for each one creates and
|
64
|
+
# executes a new plucker.
|
65
|
+
#
|
66
|
+
# This pluck gives the whole process a recursive character and options
|
67
|
+
# for that plucker may be passed in the options hash.
|
68
|
+
def build_results
|
69
|
+
super
|
70
|
+
|
71
|
+
return if @has_and_belongs_to_many_reflections.blank?
|
72
|
+
|
73
|
+
build_only_ids_has_and_belongs_to_many_reflections
|
74
|
+
build_complete_has_and_belongs_to_many_reflections
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# This method build the reflections completely, creating hashes for each record, etc.
|
80
|
+
#
|
81
|
+
# It searches reflections that has not the :only_ids option enabled and
|
82
|
+
# then creates pluckers for them.
|
83
|
+
private def build_complete_has_and_belongs_to_many_reflections
|
84
|
+
|
85
|
+
@has_and_belongs_to_many_reflections.reject {|_, reflection| reflection[:only_ids] }.each do |name, reflection|
|
86
|
+
# As an example we will imagine that we are plucking Authors and
|
87
|
+
# this relation is the :posts
|
88
|
+
|
89
|
+
# We get the meta information about the reflection
|
90
|
+
klass_reflection = @klass_reflections[name]
|
91
|
+
|
92
|
+
# initialize some options such as the plucker or the scope of the pluck
|
93
|
+
scope = reflection[:scope] || klass_reflection.klass.send(all_method)
|
94
|
+
plucker = reflection[:plucker] || Pluckers::Base
|
95
|
+
|
96
|
+
# We will use the _ids already fetched to check which records we should pluck
|
97
|
+
ids_reflection_name = "#{name.to_s.singularize}_ids".to_sym
|
98
|
+
|
99
|
+
ids_to_query = @results.map do |_, result|
|
100
|
+
result[ids_reflection_name]
|
101
|
+
end
|
102
|
+
|
103
|
+
ids_to_query = ids_to_query.flatten
|
104
|
+
|
105
|
+
|
106
|
+
|
107
|
+
# And now we create the plucker. Notice that we add a where to the
|
108
|
+
# scope, so we filter the records to pluck as we only get those with
|
109
|
+
# an id in the set of the _ids arrays already plucked
|
110
|
+
#
|
111
|
+
# In our Example we would be doing something like
|
112
|
+
# Category.all.where(id: category_ids)
|
113
|
+
reflection_plucker = plucker.new scope.where(id: ids_to_query), reflection
|
114
|
+
|
115
|
+
# We initialize so we return an empty array if there are no record
|
116
|
+
# related
|
117
|
+
@results.each do |_, result|
|
118
|
+
result[name] ||= []
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
reflection_plucker.pluck.each do |r|
|
123
|
+
@results.each do |_,result|
|
124
|
+
# For each related result (category) we search those records
|
125
|
+
# (BlogPost) that include the category id in its _ids array
|
126
|
+
if result[ids_reflection_name].include? r[:id].to_i
|
127
|
+
result[name] << r
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# And now we get rid of duplicates
|
133
|
+
@results.each do |_,result|
|
134
|
+
result[name].uniq!
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# This method build the ids for the records instead of creating the hashes.
|
142
|
+
#
|
143
|
+
# Unlike the has_many relationships, we don't search reflections that
|
144
|
+
# has the :only_ids option enabled as we will need these ids also for
|
145
|
+
# the other relationships.
|
146
|
+
private def build_only_ids_has_and_belongs_to_many_reflections
|
147
|
+
|
148
|
+
@has_and_belongs_to_many_reflections.each do |name, reflection|
|
149
|
+
# As an example we will imagine that we are plucking BlogPosts and
|
150
|
+
# this relation is the :categories one
|
151
|
+
|
152
|
+
# We get the meta information about the reflection
|
153
|
+
klass_reflection = @klass_reflections[name]
|
154
|
+
|
155
|
+
# We get the ids. This query is dependant on the ActiveRecord
|
156
|
+
# version, so every feature version has a different implementation
|
157
|
+
join_results = has_and_belongs_to_many_ids(klass_reflection)
|
158
|
+
|
159
|
+
ids_reflection_name = "#{name.to_s.singularize}_ids".to_sym
|
160
|
+
|
161
|
+
# Next, we initialize the _ids array for each result
|
162
|
+
@results.each do |_, result|
|
163
|
+
result[ids_reflection_name] ||= []
|
164
|
+
end
|
165
|
+
|
166
|
+
# And now, the foreign_keys.
|
167
|
+
# In our example with BlogPost and Category they would be:
|
168
|
+
# model_foreign_key = blog_post_id
|
169
|
+
# related_model_foreign_key = category_id
|
170
|
+
model_foreign_key = klass_reflection.foreign_key
|
171
|
+
related_model_foreign_key = klass_reflection.association_foreign_key
|
172
|
+
|
173
|
+
# And for each result we fill the results
|
174
|
+
join_results.each do |r|
|
175
|
+
@results[r[model_foreign_key].to_i][ids_reflection_name] << r[related_model_foreign_key].to_i
|
176
|
+
end
|
177
|
+
|
178
|
+
# And eliminate duplicates
|
179
|
+
@results.each do |_,result|
|
180
|
+
result[ids_reflection_name].uniq!
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
module Pluckers
|
2
|
+
##
|
3
|
+
# This module groups diferent modules that will configure and build the
|
4
|
+
# results for a specific kind of information (attributes, translations,
|
5
|
+
# relations...)
|
6
|
+
#
|
7
|
+
# All this modules will have two methods, one for configuration (e.g, attributes
|
8
|
+
# to be included in the real pluck) and one for building the final results
|
9
|
+
module Features
|
10
|
+
|
11
|
+
module Base
|
12
|
+
|
13
|
+
##
|
14
|
+
# This module implements plucking has_many relationships in a recursive
|
15
|
+
# way.
|
16
|
+
#
|
17
|
+
# The options used in this feature are:
|
18
|
+
#
|
19
|
+
# * reflections: A hash of the reflections we will pluck recursively. The
|
20
|
+
# key of this hash will be the name of the reflection and the value is
|
21
|
+
# another hash of options.
|
22
|
+
#
|
23
|
+
# - scope: You can limit the scope of the objects plucked. E.g, you
|
24
|
+
# could use Author.active instead of Author.all. Notice that .all is
|
25
|
+
# the default.
|
26
|
+
#
|
27
|
+
# - plucker: You can use a custom plucker instead of Pluckers::Base in
|
28
|
+
# case you want any specific logic. Pluckers::Base is the default one.
|
29
|
+
#
|
30
|
+
# - only_ids: We can get the _ids array instead of an array with hashes
|
31
|
+
# if we pass this option as true. If we do any fields or plucker
|
32
|
+
# option will be ignored.
|
33
|
+
#
|
34
|
+
# - Any other option will be passed to the plucker, so you can send any
|
35
|
+
# other regular option such as attributes, custom ones or even more
|
36
|
+
# reflections. Recursivity FTW!!
|
37
|
+
#
|
38
|
+
module HasManyReflections
|
39
|
+
|
40
|
+
|
41
|
+
##
|
42
|
+
# Here we obtain the has_many reflections to include in the pluck
|
43
|
+
# operation and also include the relation foreign key in the attributes to
|
44
|
+
# pluck for this model.
|
45
|
+
def configure_query
|
46
|
+
super
|
47
|
+
|
48
|
+
pluck_reflections = @options[:reflections] || {}
|
49
|
+
|
50
|
+
return if pluck_reflections.blank?
|
51
|
+
|
52
|
+
@has_many_reflections = { }
|
53
|
+
|
54
|
+
# We iterate through the class reflections passed as options
|
55
|
+
@klass_reflections.slice(*pluck_reflections.keys).
|
56
|
+
# And select those that are HasMany
|
57
|
+
select{|_, r| active_record_has_many_reflection?(r)}.
|
58
|
+
# And store them in the has_many_reflection hash that will be used later
|
59
|
+
each do |name, reflection|
|
60
|
+
name = name.to_sym
|
61
|
+
@has_many_reflections[name] = pluck_reflections[name]
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# In this method we get the reflections and for each one creates and
|
68
|
+
# executes a new plucker.
|
69
|
+
#
|
70
|
+
# This pluck gives the whole process a recursive character and options
|
71
|
+
# for that plucker may be passed in the options hash.
|
72
|
+
def build_results
|
73
|
+
super
|
74
|
+
|
75
|
+
return if @has_many_reflections.blank?
|
76
|
+
|
77
|
+
build_complete_reflections
|
78
|
+
build_only_ids_reflections
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# This method build the reflections completely, creating hashes for each record, etc.
|
84
|
+
#
|
85
|
+
# It searches reflections that has not the :only_ids option enabled and
|
86
|
+
# then creates pluckers for them.
|
87
|
+
private def build_complete_reflections
|
88
|
+
|
89
|
+
@has_many_reflections.reject {|_, reflection| reflection[:only_ids] }.each do |name, reflection|
|
90
|
+
# As an example we will imagine that we are plucking Authors and
|
91
|
+
# this relation is the :posts
|
92
|
+
|
93
|
+
# We get the meta information about the reflection
|
94
|
+
klass_reflection = @klass_reflections[name]
|
95
|
+
|
96
|
+
|
97
|
+
# initialize some options such as the plucker or the scope of the pluck
|
98
|
+
scope = reflection[:scope] || klass_reflection.klass.send(all_method)
|
99
|
+
plucker = reflection[:plucker] || Pluckers::Base
|
100
|
+
|
101
|
+
# If there are attributes configured to be plucked we add the foreign
|
102
|
+
# key as we will need it to relate the records
|
103
|
+
reflection[:attributes] |= [klass_reflection.foreign_key.to_sym] if reflection[:attributes]
|
104
|
+
|
105
|
+
|
106
|
+
# And now we create the plucker. Notice that we add a where to the
|
107
|
+
# scope, so we filter the records to pluck as we only get those with
|
108
|
+
# an id in the set of the foreign keys of the records already
|
109
|
+
# plucked by the base plucker
|
110
|
+
#
|
111
|
+
# In our Example we would be doing something like
|
112
|
+
# BlogPost.all.where(author_id: author_ids)
|
113
|
+
reflection_plucker = plucker.new scope.where(
|
114
|
+
klass_reflection.foreign_key => @results.map{|_, r| r[klass_reflection.active_record_primary_key.to_sym] }
|
115
|
+
),
|
116
|
+
reflection
|
117
|
+
|
118
|
+
# We initialize so we return an empty array if there are no record
|
119
|
+
# related
|
120
|
+
@results.each do |_, result|
|
121
|
+
result[name] ||= []
|
122
|
+
end
|
123
|
+
|
124
|
+
reflection_plucker.pluck.each do |r|
|
125
|
+
@results.each do |_,result|
|
126
|
+
# For each related result (Author) we search those records
|
127
|
+
# (BlogPost) that are related (author.id == post.author_id) and
|
128
|
+
# insert them in the relationship attributes
|
129
|
+
if result[klass_reflection.active_record_primary_key.to_sym] == r[klass_reflection.foreign_key.to_sym]
|
130
|
+
result[name] << r
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# This method build the ids for the records instead of creating the hashes.
|
140
|
+
#
|
141
|
+
# It searches reflections that has the :only_ids option enabled and then
|
142
|
+
# creates pluckers for them.
|
143
|
+
private def build_only_ids_reflections
|
144
|
+
|
145
|
+
@has_many_reflections.select {|_, reflection| reflection[:only_ids] }.each do |name, reflection|
|
146
|
+
# As an example we will imagine that we are plucking Authors and
|
147
|
+
# this relation is the :posts
|
148
|
+
|
149
|
+
# We get the meta information about the reflection
|
150
|
+
klass_reflection = @klass_reflections[name]
|
151
|
+
|
152
|
+
# We can send an scope option for filtering the related records
|
153
|
+
scope = reflection[:scope] || klass_reflection.klass.send(all_method)
|
154
|
+
|
155
|
+
# We override the attributes as we only get the required ones for
|
156
|
+
# relating the records
|
157
|
+
reflection[:attributes] = [klass_reflection.foreign_key.to_sym, klass_reflection.active_record_primary_key.to_sym]
|
158
|
+
|
159
|
+
# And now, create the plucker, filtering the records so we only get
|
160
|
+
# the related ones
|
161
|
+
reflection_plucker = Pluckers::Base.new scope.where(
|
162
|
+
klass_reflection.foreign_key => @results.map{|_, r| r[klass_reflection.active_record_primary_key.to_sym] }
|
163
|
+
),
|
164
|
+
reflection
|
165
|
+
|
166
|
+
# Now we create the _ids attribute for each result
|
167
|
+
ids_reflection_name = "#{name.to_s.singularize}_ids".to_sym
|
168
|
+
|
169
|
+
@results.each do |_, result|
|
170
|
+
result[ids_reflection_name] ||= []
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
reflection_plucker.pluck.each do |r|
|
175
|
+
@results.
|
176
|
+
each do |_,result|
|
177
|
+
# For each related result (Author) we search those records
|
178
|
+
# (BlogPost) that are related (author.id == post.author_id) and
|
179
|
+
# insert the id in the _ids array
|
180
|
+
if result[klass_reflection.active_record_primary_key.to_sym] == r[klass_reflection.foreign_key.to_sym]
|
181
|
+
result[ids_reflection_name] << r[klass_reflection.active_record_primary_key.to_sym]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Pluckers
|
2
|
+
##
|
3
|
+
# This module groups diferent modules that will configure and build the
|
4
|
+
# results for a specific kind of information (attributes, translations,
|
5
|
+
# relations...)
|
6
|
+
#
|
7
|
+
# All this modules will have two methods, one for configuration (e.g, attributes
|
8
|
+
# to be included in the real pluck) and one for building the final results
|
9
|
+
module Features
|
10
|
+
|
11
|
+
module Base
|
12
|
+
|
13
|
+
##
|
14
|
+
# This module implements plucking has_many :through relationships in a
|
15
|
+
# recursive way.
|
16
|
+
#
|
17
|
+
# The options used in this feature are:
|
18
|
+
#
|
19
|
+
# * reflections: A hash of the reflections we will pluck recursively. The
|
20
|
+
# key of this hash will be the name of the reflection and the value is
|
21
|
+
# another hash of options.
|
22
|
+
#
|
23
|
+
# - scope: You can limit the scope of the objects plucked. E.g, you
|
24
|
+
# could use Author.active instead of Author.all. Notice that .all is
|
25
|
+
# the default.
|
26
|
+
#
|
27
|
+
# - plucker: You can use a custom plucker instead of Pluckers::Base in
|
28
|
+
# case you want any specific logic. Pluckers::Base is the default one.
|
29
|
+
#
|
30
|
+
# - Any other option will be passed to the plucker, so you can send any
|
31
|
+
# other regular option such as attributes, custom ones or even more
|
32
|
+
# reflections. Recursivity FTW!!
|
33
|
+
#
|
34
|
+
module HasManyThroughReflections
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
# Here we obtain the has_many :through reflections to include in the pluck
|
39
|
+
# operation and also include the relation foreign key in the attributes to
|
40
|
+
# pluck for this model.
|
41
|
+
def configure_query
|
42
|
+
super
|
43
|
+
|
44
|
+
pluck_reflections = @options[:reflections] || {}
|
45
|
+
|
46
|
+
return if pluck_reflections.blank?
|
47
|
+
|
48
|
+
@has_many_through_reflections = { }
|
49
|
+
|
50
|
+
# We iterate through the class reflections passed as options
|
51
|
+
@klass_reflections.slice(*pluck_reflections.keys).
|
52
|
+
# And select those that are Through and which delegate reflection is a HasMany
|
53
|
+
select{|_, r| active_record_has_many_through_reflection?(r)}.
|
54
|
+
# And store them in the has_many_reflection hash that will be used later
|
55
|
+
each do |name, reflection|
|
56
|
+
name = name.to_sym
|
57
|
+
@has_many_through_reflections[name] = pluck_reflections[name]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# In this method we get the reflections and for each one creates and
|
63
|
+
# executes a new plucker.
|
64
|
+
#
|
65
|
+
# This pluck gives the whole process a recursive character and options
|
66
|
+
# for that plucker may be passed in the options hash.
|
67
|
+
def build_results
|
68
|
+
super
|
69
|
+
|
70
|
+
return if @has_many_through_reflections.blank?
|
71
|
+
|
72
|
+
@has_many_through_reflections.each do |name, reflection|
|
73
|
+
# As an example we will imagine that we are plucking Authors and
|
74
|
+
# this relation is the :references, that is a relationship through
|
75
|
+
# BlogPost
|
76
|
+
|
77
|
+
# We get the meta information about the :through reflection (:references)
|
78
|
+
klass_reflection = @klass_reflections[name]
|
79
|
+
|
80
|
+
# And also the has_many reflection (:posts) as we wiil need it to fetch information
|
81
|
+
reflection_to_pluck = klass_reflection.chain.reverse.first
|
82
|
+
|
83
|
+
# initialize some options such as the plucker or the scope of the pluck
|
84
|
+
scope = reflection_to_pluck.klass.send(all_method)
|
85
|
+
|
86
|
+
# Essentially we are going to pluck the has_many relationship and
|
87
|
+
# add the reflections option so it recursively plucks the has_many
|
88
|
+
# :references reflection from BlogPost.
|
89
|
+
plucker = reflection[:plucker] || Pluckers::Base
|
90
|
+
plucker_options = {
|
91
|
+
attributes: [reflection_to_pluck.foreign_key.to_sym],
|
92
|
+
reflections: { klass_reflection.source_reflection.name => reflection }
|
93
|
+
}
|
94
|
+
|
95
|
+
# In order to create this intermediary plucker we add a where to the
|
96
|
+
# scope, so we filter the records to pluck as we only get those with
|
97
|
+
# an id in the set of the foreign keys of the records already
|
98
|
+
# plucked by the base plucker
|
99
|
+
#
|
100
|
+
# In our Example we would be doing something like
|
101
|
+
# BlogPost.all.where(author_id: author_ids)
|
102
|
+
reflection_plucker = plucker.new scope.where(
|
103
|
+
reflection_to_pluck.foreign_key => @results.map{|_, r| r[reflection_to_pluck.active_record_primary_key.to_sym] }
|
104
|
+
),
|
105
|
+
plucker_options
|
106
|
+
|
107
|
+
# We initialize so we return an empty array if there are no record
|
108
|
+
# related
|
109
|
+
@results.each do |_, result|
|
110
|
+
result[name] ||= []
|
111
|
+
end
|
112
|
+
|
113
|
+
reflection_plucker.pluck.each do |r|
|
114
|
+
@results.each do |_,result|
|
115
|
+
# For each related result (BlogPost) we search those records
|
116
|
+
# (Author) that are related (author.id == post.author_id) and
|
117
|
+
# insert not the record itself but the desired reflection in the
|
118
|
+
# result
|
119
|
+
if result[reflection_to_pluck.active_record_primary_key.to_sym] == r[reflection_to_pluck.foreign_key.to_sym] &&
|
120
|
+
r[klass_reflection.source_reflection.name]
|
121
|
+
|
122
|
+
result[name] += [r[klass_reflection.source_reflection.name]].flatten
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|