deep_pluck 1.1.2 → 1.1.7

Sign up to get free protection for your applications and to get access to all the features.
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