deep_pluck 1.1.2 → 1.1.7

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.
data/bin/console CHANGED
File without changes
data/bin/setup CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install --gemfile=gemfiles/4.2.gemfile
7
-
8
- # Do any other automated setup that you need to do here
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install --gemfile=gemfiles/4.2.gemfile
7
+
8
+ # Do any other automated setup that you need to do here
data/deep_pluck.gemspec CHANGED
@@ -1,44 +1,45 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'deep_pluck/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = 'deep_pluck'
8
- spec.version = DeepPluck::VERSION
9
- spec.authors = ['khiav reoy']
10
- spec.email = ['mrtmrt15xn@yahoo.com.tw']
11
-
12
- spec.summary = 'Use deep_pluck as a shortcut to select one or more attributes and include associated models without loading a bunch of records.'
13
- spec.description = 'Use deep_pluck as a shortcut to select one or more attributes and include associated models without loading a bunch of records. And DRY up your code when using #as_json.'
14
- spec.homepage = 'https://github.com/khiav223577/deep_pluck'
15
- spec.license = 'MIT'
16
-
17
- # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
- # delete this section to allow pushing this gem to any host.
19
- # if spec.respond_to?(:metadata)
20
- # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
- # else
22
- # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
- # end
24
-
25
- spec.files = `git ls-files -z`.split("\x0").reject{|f| f.match(%r{^(test|spec|features)/}) }
26
- spec.bindir = 'exe'
27
- spec.executables = spec.files.grep(%r{^exe/}){|f| File.basename(f) }
28
- spec.require_paths = ['lib']
29
- spec.metadata = {
30
- 'homepage_uri' => 'https://github.com/khiav223577/deep_pluck',
31
- 'changelog_uri' => 'https://github.com/khiav223577/deep_pluck/blob/master/CHANGELOG.md',
32
- 'source_code_uri' => 'https://github.com/khiav223577/deep_pluck',
33
- 'documentation_uri' => 'https://www.rubydoc.info/gems/deep_pluck',
34
- 'bug_tracker_uri' => 'https://github.com/khiav223577/deep_pluck/issues',
35
- }
36
-
37
- spec.add_development_dependency 'bundler', '>= 1.17', '< 3.x'
38
- spec.add_development_dependency 'rake', '~> 12.0'
39
- spec.add_development_dependency 'sqlite3', '~> 1.3'
40
- spec.add_development_dependency 'minitest', '~> 5.0'
41
-
42
- spec.add_dependency 'activerecord', '>= 3'
43
- spec.add_dependency 'pluck_all', '>= 1.2.3'
44
- end
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'deep_pluck/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'deep_pluck'
8
+ spec.version = DeepPluck::VERSION
9
+ spec.authors = ['khiav reoy']
10
+ spec.email = ['mrtmrt15xn@yahoo.com.tw']
11
+
12
+ spec.summary = 'Use deep_pluck as a shortcut to select one or more attributes and include associated models without loading a bunch of records.'
13
+ spec.description = 'Use deep_pluck as a shortcut to select one or more attributes and include associated models without loading a bunch of records. And DRY up your code when using #as_json.'
14
+ spec.homepage = 'https://github.com/khiav223577/deep_pluck'
15
+ spec.license = 'MIT'
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject{|f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = 'exe'
27
+ spec.executables = spec.files.grep(%r{^exe/}){|f| File.basename(f) }
28
+ spec.require_paths = ['lib']
29
+ spec.metadata = {
30
+ 'homepage_uri' => 'https://github.com/khiav223577/deep_pluck',
31
+ 'changelog_uri' => 'https://github.com/khiav223577/deep_pluck/blob/master/CHANGELOG.md',
32
+ 'source_code_uri' => 'https://github.com/khiav223577/deep_pluck',
33
+ 'documentation_uri' => 'https://www.rubydoc.info/gems/deep_pluck',
34
+ 'bug_tracker_uri' => 'https://github.com/khiav223577/deep_pluck/issues',
35
+ }
36
+
37
+ spec.add_development_dependency 'bundler', '>= 1.17', '< 3.x'
38
+ spec.add_development_dependency 'rake', '~> 12.0'
39
+ spec.add_development_dependency 'sqlite3', '~> 1.3'
40
+ spec.add_development_dependency 'minitest', '~> 5.0'
41
+
42
+ spec.add_dependency 'activerecord', '>= 3'
43
+ spec.add_dependency 'pluck_all', '>= 1.2.3'
44
+ spec.add_dependency 'rails_compatibility', '>= 0.0.4'
45
+ end
data/gemfiles/3.2.gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 3.2.0'
4
- gem 'pluck_all', '~> 1.2.2'
5
-
6
- group :test do
7
- gem 'simplecov'
8
- gem 'sqlite3', '~> 1.3.6'
9
- end
10
-
11
- gemspec path: '../'
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 3.2.0'
4
+ gem 'pluck_all', '~> 1.2.2'
5
+
6
+ group :test do
7
+ gem 'simplecov', '< 0.18'
8
+ gem 'sqlite3', '~> 1.3.6'
9
+ end
10
+
11
+ gemspec path: '../'
data/gemfiles/4.2.gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 4.2.0'
4
- gem 'pluck_all', '~> 1.2.2'
5
-
6
- group :test do
7
- gem 'simplecov'
8
- gem 'sqlite3', '~> 1.3.6'
9
- end
10
-
11
- gemspec path: '../'
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 4.2.0'
4
+ gem 'pluck_all', '~> 1.2.2'
5
+
6
+ group :test do
7
+ gem 'simplecov', '< 0.18'
8
+ gem 'sqlite3', '~> 1.3.6'
9
+ end
10
+
11
+ gemspec path: '../'
data/gemfiles/5.0.gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 5.0.0'
4
- gem 'pluck_all', '~> 1.2.2'
5
-
6
- group :test do
7
- gem 'simplecov'
8
- gem 'sqlite3', '~> 1.3.6'
9
- end
10
-
11
- gemspec path: '../'
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 5.0.0'
4
+ gem 'pluck_all', '~> 1.2.2'
5
+
6
+ group :test do
7
+ gem 'simplecov', '< 0.18'
8
+ gem 'sqlite3', '~> 1.3.6'
9
+ end
10
+
11
+ gemspec path: '../'
data/gemfiles/5.1.gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 5.1.0'
4
- gem 'pluck_all', '~> 1.2.2'
5
-
6
- group :test do
7
- gem 'simplecov'
8
- gem 'sqlite3', '~> 1.3.6'
9
- end
10
-
11
- gemspec path: '../'
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 5.1.0'
4
+ gem 'pluck_all', '~> 1.2.2'
5
+
6
+ group :test do
7
+ gem 'simplecov', '< 0.18'
8
+ gem 'sqlite3', '~> 1.3.6'
9
+ end
10
+
11
+ gemspec path: '../'
data/gemfiles/5.2.gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 5.2.0'
4
- gem 'pluck_all', '~> 1.2.2'
5
-
6
- group :test do
7
- gem 'simplecov'
8
- gem 'sqlite3', '~> 1.3.6'
9
- end
10
-
11
- gemspec path: '../'
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 5.2.0'
4
+ gem 'pluck_all', '~> 1.2.2'
5
+
6
+ group :test do
7
+ gem 'simplecov', '< 0.18'
8
+ gem 'sqlite3', '~> 1.3.6'
9
+ end
10
+
11
+ gemspec path: '../'
data/gemfiles/6.0.gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 6.0.0'
4
- gem 'pluck_all', '~> 2.0.4'
5
-
6
- group :test do
7
- gem 'simplecov'
8
- gem 'sqlite3', '~> 1.4.1'
9
- end
10
-
11
- gemspec path: '../'
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 6.0.0'
4
+ gem 'pluck_all', '~> 2.0.4'
5
+
6
+ group :test do
7
+ gem 'simplecov', '< 0.18'
8
+ gem 'sqlite3', '~> 1.4.1'
9
+ end
10
+
11
+ gemspec path: '../'
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activerecord', '~> 6.1.1'
4
+ gem 'pluck_all', '~> 2.2.1'
5
+
6
+ group :test do
7
+ gem 'simplecov', '< 0.18'
8
+ gem 'sqlite3', '~> 1.4.1'
9
+ end
10
+
11
+ gemspec path: '../'
data/lib/deep_pluck.rb CHANGED
@@ -1,23 +1,23 @@
1
- require 'deep_pluck/version'
2
- require 'deep_pluck/model'
3
- require 'active_record'
4
- require 'pluck_all'
5
-
6
- class ActiveRecord::Relation
7
- def deep_pluck(*args)
8
- DeepPluck::Model.new(self).add(args).load_all
9
- end
10
- end
11
-
12
- class ActiveRecord::Base
13
- def self.deep_pluck(*args)
14
- where('').deep_pluck(*args)
15
- end
16
-
17
- def deep_pluck(*args)
18
- hash_args, other_args = args.partition{|s| s.is_a?(Hash) }
19
- model = DeepPluck::Model.new(self, need_columns: other_args)
20
- model.add(*hash_args) if hash_args.any?
21
- return model.load_all.first
22
- end
23
- end
1
+ require 'deep_pluck/version'
2
+ require 'deep_pluck/model'
3
+ require 'active_record'
4
+ require 'pluck_all'
5
+
6
+ class ActiveRecord::Relation
7
+ def deep_pluck(*args)
8
+ DeepPluck::Model.new(self).add(args).load_all
9
+ end
10
+ end
11
+
12
+ class ActiveRecord::Base
13
+ def self.deep_pluck(*args)
14
+ where('').deep_pluck(*args)
15
+ end
16
+
17
+ def deep_pluck(*args)
18
+ hash_args, other_args = args.partition{|s| s.is_a?(Hash) }
19
+ model = DeepPluck::Model.new(self, need_columns: other_args)
20
+ model.add(*hash_args) if hash_args.any?
21
+ return model.load_all.first
22
+ end
23
+ end
@@ -26,8 +26,8 @@ module DeepPluck
26
26
  def assign_values_to_parent(collection, parent, children_hash, column_name, foreign_key, reverse: false)
27
27
  parent.each do |s|
28
28
  next if (id = s[foreign_key]) == nil
29
- left = reverse ? children_hash[id] : s
30
- right = !reverse ? children_hash[id] : s
29
+ left = reverse ? children_hash[id] : s
30
+ right = !reverse ? children_hash[id] : s
31
31
  if collection
32
32
  left[column_name] << right
33
33
  else
@@ -1,196 +1,229 @@
1
- require 'deep_pluck/data_combiner'
2
-
3
- module DeepPluck
4
- class Model
5
- # ----------------------------------------------------------------
6
- # ● Initialize
7
- # ----------------------------------------------------------------
8
- def initialize(relation, parent_association_key = nil, parent_model = nil, need_columns: [])
9
- if relation.is_a?(ActiveRecord::Base)
10
- @model = relation
11
- @relation = nil
12
- @klass = @model.class
13
- else
14
- @model = nil
15
- @relation = relation
16
- @klass = @relation.klass
17
- end
18
-
19
- @parent_association_key = parent_association_key
20
- @parent_model = parent_model
21
- @need_columns = need_columns
22
- @associations = {}
23
- end
24
-
25
- # ----------------------------------------------------------------
26
- # ● Reader
27
- # ----------------------------------------------------------------
28
- def get_reflect(association_key)
29
- @klass.reflect_on_association(association_key.to_sym) || # add to_sym since rails 3 only support symbol
30
- fail(ActiveRecord::ConfigurationError, "ActiveRecord::ConfigurationError: Association named \
31
- '#{association_key}' was not found on #{@klass.name}; perhaps you misspelled it?"
32
- )
33
- end
34
-
35
- def with_conditions(reflect, relation)
36
- options = reflect.options
37
- relation = relation.instance_exec(&reflect.scope) if reflect.respond_to?(:scope) and reflect.scope
38
- relation = relation.where(options[:conditions]) if options[:conditions]
39
- return relation
40
- end
41
-
42
- def get_join_table(reflect)
43
- options = reflect.options
44
- return options[:through] if options[:through]
45
- return (options[:join_table] || reflect.send(:derive_join_table)) if reflect.macro == :has_and_belongs_to_many
46
- return nil
47
- end
48
-
49
- def get_primary_key(reflect)
50
- return (reflect.belongs_to? ? reflect.klass : reflect.active_record).primary_key
51
- end
52
-
53
- def get_foreign_key(reflect, reverse: false, with_table_name: false)
54
- if reverse and (table_name = get_join_table(reflect)) # reverse = parent
55
- key = reflect.chain.last.foreign_key
56
- else
57
- key = (reflect.belongs_to? == reverse ? get_primary_key(reflect) : reflect.foreign_key)
58
- table_name = (reverse ? reflect.klass : reflect.active_record).table_name
59
- end
60
- return "#{table_name}.#{key}" if with_table_name
61
- return key.to_s # key may be symbol if specify foreign_key in association options
62
- end
63
-
64
- # ----------------------------------------------------------------
65
- # Contruction OPs
66
- # ----------------------------------------------------------------
67
-
68
- private
69
-
70
- def add_need_column(column)
71
- @need_columns << column.to_s
72
- end
73
-
74
- def add_association(hash)
75
- hash.each do |key, value|
76
- model = (@associations[key] ||= Model.new(get_reflect(key).klass.where(''), key, self))
77
- model.add(value)
78
- end
79
- end
80
-
81
- public
82
-
83
- def add(args)
84
- return self if args == nil
85
- args = [args] if not args.is_a?(Array)
86
- args.each do |arg|
87
- case arg
88
- when Hash ; add_association(arg)
89
- else ; add_need_column(arg)
90
- end
91
- end
92
- return self
93
- end
94
-
95
- # ----------------------------------------------------------------
96
- # Load
97
- # ----------------------------------------------------------------
98
- private
99
-
100
- def do_query(parent, reflect, relation)
101
- parent_key = get_foreign_key(reflect)
102
- relation_key = get_foreign_key(reflect, reverse: true, with_table_name: true)
103
- ids = parent.map{|s| s[parent_key] }
104
- ids.uniq!
105
- ids.compact!
106
- relation = with_conditions(reflect, relation)
107
- query = { relation_key => ids }
108
- query[reflect.type] = reflect.active_record.to_s if reflect.type
109
- return relation.joins(get_join_table(reflect)).where(query)
110
- end
111
-
112
- def set_includes_data(parent, column_name, model)
113
- reflect = get_reflect(column_name)
114
- reverse = !reflect.belongs_to?
115
- foreign_key = get_foreign_key(reflect, reverse: reverse)
116
- primary_key = get_primary_key(reflect)
117
- children = model.load_data{|relation| do_query(parent, reflect, relation) }
118
- # reverse = false: Child.where(:id => parent.pluck(:child_id))
119
- # reverse = true : Child.where(:parent_id => parent.pluck(:id))
120
- return DataCombiner.combine_data(
121
- parent,
122
- children,
123
- primary_key,
124
- column_name,
125
- foreign_key,
126
- reverse,
127
- reflect.collection?,
128
- )
129
- end
130
-
131
- def get_query_columns
132
- if @parent_model
133
- parent_reflect = @parent_model.get_reflect(@parent_association_key)
134
- prev_need_columns = @parent_model.get_foreign_key(parent_reflect, reverse: true, with_table_name: true)
135
- end
136
- next_need_columns = @associations.map{|key, _| get_foreign_key(get_reflect(key), with_table_name: true) }.uniq
137
- return [*prev_need_columns, *next_need_columns, *@need_columns].uniq(&Helper::TO_KEY_PROC)
138
- end
139
-
140
- def pluck_values(columns)
141
- includes_values = @relation.includes_values
142
- @relation.includes_values = []
143
-
144
- result = @relation.pluck_all(*columns)
145
-
146
- @relation.includes_values = includes_values
147
- return result
148
- end
149
-
150
- def loaded_models
151
- return [@model] if @model
152
- return @relation if @relation.loaded
153
- end
154
-
155
- public
156
-
157
- def load_data
158
- columns = get_query_columns
159
- key_columns = columns.map(&Helper::TO_KEY_PROC)
160
- @relation = yield(@relation) if block_given?
161
- @data = loaded_models ? loaded_models.as_json(root: false, only: key_columns) : pluck_values(columns)
162
- if @data.size != 0
163
- # for delete_extra_column_data!
164
- @extra_columns = key_columns - @need_columns.map(&Helper::TO_KEY_PROC)
165
- @associations.each do |key, model|
166
- set_includes_data(@data, key, model)
167
- end
168
- end
169
- return @data
170
- end
171
-
172
- def load_all
173
- load_data
174
- delete_extra_column_data!
175
- return @data
176
- end
177
-
178
- def delete_extra_column_data!
179
- return if @data.blank?
180
- @data.each{|s| s.except!(*@extra_columns) }
181
- @associations.each{|_, model| model.delete_extra_column_data! }
182
- end
183
-
184
- # ----------------------------------------------------------------
185
- # Helper methods
186
- # ----------------------------------------------------------------
187
- module Helper
188
- TO_KEY_PROC = proc{|s| Helper.column_to_key(s) }
189
- def self.column_to_key(key) # user_achievements.user_id => user_id
190
- key = key[/(\w+)[^\w]*\z/]
191
- key.gsub!(/[^\w]+/, '')
192
- return key
193
- end
194
- end
195
- end
196
- end
1
+ require 'rails_compatibility'
2
+ require 'rails_compatibility/unscope_where'
3
+ require 'rails_compatibility/build_joins'
4
+ require 'deep_pluck/data_combiner'
5
+
6
+ module DeepPluck
7
+ class Model
8
+ # ----------------------------------------------------------------
9
+ # ● Initialize
10
+ # ----------------------------------------------------------------
11
+ def initialize(relation, parent_association_key = nil, parent_model = nil, need_columns: [])
12
+ if relation.is_a?(ActiveRecord::Base)
13
+ @model = relation
14
+ @relation = nil
15
+ @klass = @model.class
16
+ else
17
+ @model = nil
18
+ @relation = relation
19
+ @klass = @relation.klass
20
+ end
21
+
22
+ @parent_association_key = parent_association_key
23
+ @parent_model = parent_model
24
+ @need_columns = need_columns
25
+ @associations = {}
26
+ end
27
+
28
+ # ----------------------------------------------------------------
29
+ # Reader
30
+ # ----------------------------------------------------------------
31
+ def get_reflect(association_key)
32
+ @klass.reflect_on_association(association_key.to_sym) || # add to_sym since rails 3 only support symbol
33
+ fail(ActiveRecord::ConfigurationError, "ActiveRecord::ConfigurationError: Association named \
34
+ '#{association_key}' was not found on #{@klass.name}; perhaps you misspelled it?"
35
+ )
36
+ end
37
+
38
+ def with_conditions(reflect, relation)
39
+ options = reflect.options
40
+ relation = relation.instance_exec(&reflect.scope) if reflect.respond_to?(:scope) and reflect.scope
41
+ relation = relation.where(options[:conditions]) if options[:conditions]
42
+ return relation
43
+ end
44
+
45
+ def get_join_table(reflect)
46
+ options = reflect.options
47
+ return options[:through] if options[:through]
48
+ return (options[:join_table] || reflect.send(:derive_join_table)) if reflect.macro == :has_and_belongs_to_many
49
+ return nil
50
+ end
51
+
52
+ def get_primary_key(reflect)
53
+ return (reflect.belongs_to? ? reflect.klass : reflect.active_record).primary_key
54
+ end
55
+
56
+ def get_foreign_key(reflect, reverse: false, with_table_name: false)
57
+ reflect = reflect.chain.last
58
+ if reverse and (table_name = get_join_table(reflect)) # reverse = parent
59
+ key = reflect.chain.last.foreign_key
60
+ else
61
+ key = (reflect.belongs_to? == reverse ? get_primary_key(reflect) : reflect.foreign_key)
62
+ table_name = (reverse ? reflect.klass : reflect.active_record).table_name
63
+ end
64
+ return "#{table_name}.#{key}" if with_table_name
65
+ return key.to_s # key may be symbol if specify foreign_key in association options
66
+ end
67
+
68
+ def get_association_scope(reflect)
69
+ RailsCompatibility.unscope_where(reflect.association_class.new({}, reflect).send(:association_scope))
70
+ end
71
+
72
+ def use_association_to_query?(reflect)
73
+ reflect.through_reflection && reflect.chain.first.macro == :has_one
74
+ end
75
+
76
+ # ----------------------------------------------------------------
77
+ # ● Contruction OPs
78
+ # ----------------------------------------------------------------
79
+
80
+ private
81
+
82
+ def add_need_column(column)
83
+ @need_columns << column
84
+ end
85
+
86
+ def add_association(hash)
87
+ hash.each do |key, value|
88
+ model = (@associations[key] ||= Model.new(get_reflect(key).klass.where(''), key, self))
89
+ model.add(value)
90
+ end
91
+ end
92
+
93
+ public
94
+
95
+ def add(args)
96
+ return self if args == nil
97
+ args = [args] if not args.is_a?(Array)
98
+ args.each do |arg|
99
+ case arg
100
+ when Hash ; add_association(arg)
101
+ else ; add_need_column(arg)
102
+ end
103
+ end
104
+ return self
105
+ end
106
+
107
+ # ----------------------------------------------------------------
108
+ # Load
109
+ # ----------------------------------------------------------------
110
+ private
111
+
112
+ def do_query(parent, reflect, relation)
113
+ parent_key = get_foreign_key(reflect)
114
+ relation_key = get_foreign_key(reflect, reverse: true, with_table_name: true)
115
+ ids = parent.map{|s| s[parent_key] }
116
+ ids.uniq!
117
+ ids.compact!
118
+ relation = with_conditions(reflect, relation)
119
+ query = { relation_key => ids }
120
+ query[reflect.type] = reflect.active_record.to_s if reflect.type
121
+
122
+ return get_association_scope(reflect).where(query) if use_association_to_query?(reflect)
123
+
124
+ joins = if reflect.macro == :has_and_belongs_to_many
125
+ RailsCompatibility.build_joins(reflect, relation)[0]
126
+ else
127
+ backtrace_possible_association(relation, get_join_table(reflect))
128
+ end
129
+
130
+ return relation.joins(joins).where(query)
131
+ end
132
+
133
+ # Let city has_many :users, through: :schools
134
+ # And the query is: City.deep_pluck('users' => :name)
135
+ # We want to get the users data via `User.joins(:school).where(city_id: city_ids)`
136
+ # But get_join_table(reflect) returns `:schools` not :school
137
+ # No idea how to get the right association, so we try singularize or pluralize it.
138
+ def backtrace_possible_association(relation, join_table)
139
+ return join_table if relation.reflect_on_association(join_table)
140
+ join_table.to_s.singularize.to_sym.tap{|s| return s if relation.reflect_on_association(s) }
141
+ join_table.to_s.pluralize.to_sym.tap{|s| return s if relation.reflect_on_association(s) }
142
+ return nil
143
+ end
144
+
145
+ def set_includes_data(parent, column_name, model)
146
+ reflect = get_reflect(column_name)
147
+ reverse = !reflect.belongs_to?
148
+ foreign_key = get_foreign_key(reflect, reverse: reverse)
149
+ primary_key = get_foreign_key(reflect, reverse: !reverse)
150
+ children = model.load_data{|relation| do_query(parent, reflect, relation) }
151
+ # reverse = false: Child.where(:id => parent.pluck(:child_id))
152
+ # reverse = true : Child.where(:parent_id => parent.pluck(:id))
153
+ return DataCombiner.combine_data(
154
+ parent,
155
+ children,
156
+ primary_key,
157
+ column_name,
158
+ foreign_key,
159
+ reverse,
160
+ reflect.collection?,
161
+ )
162
+ end
163
+
164
+ def get_query_columns
165
+ if @parent_model
166
+ parent_reflect = @parent_model.get_reflect(@parent_association_key)
167
+ prev_need_columns = @parent_model.get_foreign_key(parent_reflect, reverse: true, with_table_name: true)
168
+ end
169
+ next_need_columns = @associations.map{|key, _| get_foreign_key(get_reflect(key), with_table_name: true) }.uniq
170
+ return [*prev_need_columns, *next_need_columns, *@need_columns].uniq(&Helper::TO_KEY_PROC)
171
+ end
172
+
173
+ def pluck_values(columns)
174
+ includes_values = @relation.includes_values
175
+ @relation.includes_values = []
176
+
177
+ result = @relation.pluck_all(*columns)
178
+
179
+ @relation.includes_values = includes_values
180
+ return result
181
+ end
182
+
183
+ def loaded_models
184
+ return [@model] if @model
185
+ return @relation if @relation.loaded
186
+ end
187
+
188
+ public
189
+
190
+ def load_data
191
+ columns = get_query_columns
192
+ key_columns = columns.map(&Helper::TO_KEY_PROC)
193
+ @relation = yield(@relation) if block_given?
194
+ @data = loaded_models ? loaded_models.as_json(root: false, only: key_columns) : pluck_values(columns)
195
+ if @data.size != 0
196
+ # for delete_extra_column_data!
197
+ @extra_columns = key_columns - @need_columns.map(&Helper::TO_KEY_PROC)
198
+ @associations.each do |key, model|
199
+ set_includes_data(@data, key, model)
200
+ end
201
+ end
202
+ return @data
203
+ end
204
+
205
+ def load_all
206
+ load_data
207
+ delete_extra_column_data!
208
+ return @data
209
+ end
210
+
211
+ def delete_extra_column_data!
212
+ return if @data.blank?
213
+ @data.each{|s| s.except!(*@extra_columns) }
214
+ @associations.each{|_, model| model.delete_extra_column_data! }
215
+ end
216
+
217
+ # ----------------------------------------------------------------
218
+ # ● Helper methods
219
+ # ----------------------------------------------------------------
220
+ module Helper
221
+ TO_KEY_PROC = proc{|s| Helper.column_to_key(s) }
222
+ def self.column_to_key(key) # user_achievements.user_id => user_id
223
+ key = key[/(\w+)[^\w]*\z/]
224
+ key.gsub!(/[^\w]+/, '')
225
+ return key
226
+ end
227
+ end
228
+ end
229
+ end