deep_pluck 1.1.1 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
3
 
4
4
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
8
  end
9
9
 
10
- task :default => :test
10
+ task default: :test
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "deep_pluck"
3
+ require 'bundler/setup'
4
+ require 'deep_pluck'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,5 @@ require "deep_pluck"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start
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,38 +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 = %q{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 = %q{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
-
30
- spec.add_development_dependency "bundler", "~> 1.11"
31
- spec.add_development_dependency "rake", "~> 12.0"
32
- spec.add_development_dependency "sqlite3", "~> 1.3"
33
- spec.add_development_dependency "minitest", "~> 5.0"
34
-
35
- spec.add_dependency "activerecord", ">= 3"
36
- spec.add_dependency "pluck_all", ">= 1.2.3"
37
-
38
- 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.3'
45
+ end
data/gemfiles/3.2.gemfile CHANGED
@@ -1,14 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in pluck_all.gemspec
4
-
5
- gem "activerecord", "~> 3.2.0"
6
- gem "pluck_all", "~> 1.2.2"
7
-
8
- group :test do
9
- gem "simplecov"
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
- gemspec :path => "../"
14
-
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,14 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in pluck_all.gemspec
4
-
5
- gem "activerecord", "~> 4.2.0"
6
- gem "pluck_all", "~> 1.2.2"
7
-
8
- group :test do
9
- gem "simplecov"
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
- gemspec :path => "../"
14
-
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,14 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in pluck_all.gemspec
4
-
5
- gem "activerecord", "~> 5.0.0"
6
- gem "pluck_all", "~> 1.2.2"
7
-
8
- group :test do
9
- gem "simplecov"
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
- gemspec :path => "../"
14
-
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,14 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in pluck_all.gemspec
4
-
5
- gem "activerecord", "~> 5.1.0"
6
- gem "pluck_all", "~> 1.2.2"
7
-
8
- group :test do
9
- gem "simplecov"
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
- gemspec :path => "../"
14
-
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,14 +1,11 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in pluck_all.gemspec
4
-
5
- gem "activerecord", "~> 5.2.0"
6
- gem "pluck_all", "~> 1.2.2"
7
-
8
- group :test do
9
- gem "simplecov"
10
- gem "codeclimate-test-reporter", "~> 1.0.0"
11
- end
12
-
13
- gemspec :path => "../"
14
-
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: '../'
@@ -0,0 +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', '< 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,24 +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
- self.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
- preloaded_model = DeepPluck::PreloadedModel.new(self, other_args)
20
- model = DeepPluck::Model.new(self.class.where(id: id), preloaded_model: preloaded_model)
21
- model.add(*hash_args) if hash_args.any?
22
- return model.load_all.first
23
- end
24
- 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
@@ -12,7 +12,7 @@ module DeepPluck
12
12
  private
13
13
 
14
14
  def make_data_hash(collection, parent, primary_key, column_name)
15
- return parent.map{|s| [s[primary_key], s]}.to_h if !collection
15
+ return parent.map{|s| [s[primary_key], s] }.to_h if !collection
16
16
  hash = {}
17
17
  parent.each do |model_hash|
18
18
  key = model_hash[primary_key]
@@ -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,173 +1,229 @@
1
- require 'deep_pluck/preloaded_model'
2
- require 'deep_pluck/data_combiner'
3
- module DeepPluck
4
- class Model
5
- # ----------------------------------------------------------------
6
- # ● Initialize
7
- # ----------------------------------------------------------------
8
- def initialize(relation, parent_association_key = nil, parent_model = nil, preloaded_model: nil)
9
- @relation = relation
10
- @preloaded_model = preloaded_model
11
- @parent_association_key = parent_association_key
12
- @parent_model = parent_model
13
- @need_columns = (preloaded_model ? preloaded_model.need_columns : [])
14
- @associations = {}
15
- end
16
-
17
- # ----------------------------------------------------------------
18
- # Reader
19
- # ----------------------------------------------------------------
20
- def get_reflect(association_key)
21
- @relation.klass.reflect_on_association(association_key.to_sym) || # add to_sym since rails 3 only support symbol
22
- fail(ActiveRecord::ConfigurationError, "ActiveRecord::ConfigurationError: Association named \
23
- '#{association_key}' was not found on #{@relation.klass.name}; perhaps you misspelled it?"
24
- )
25
- end
26
-
27
- def with_conditions(reflect, relation)
28
- options = reflect.options
29
- relation = relation.instance_exec(&reflect.scope) if reflect.respond_to?(:scope) and reflect.scope
30
- relation = relation.where(options[:conditions]) if options[:conditions]
31
- return relation
32
- end
33
-
34
- def get_join_table(reflect)
35
- options = reflect.options
36
- return options[:through] if options[:through]
37
- return (options[:join_table] || reflect.send(:derive_join_table)) if reflect.macro == :has_and_belongs_to_many
38
- return nil
39
- end
40
-
41
- def get_primary_key(reflect)
42
- return (reflect.belongs_to? ? reflect.klass : reflect.active_record).primary_key
43
- end
44
-
45
- def get_foreign_key(reflect, reverse: false, with_table_name: false)
46
- if reverse and (table_name = get_join_table(reflect)) # reverse = parent
47
- key = reflect.chain.last.foreign_key
48
- else
49
- key = (reflect.belongs_to? == reverse ? get_primary_key(reflect) : reflect.foreign_key)
50
- table_name = (reverse ? reflect.klass : reflect.active_record).table_name
51
- end
52
- return "#{table_name}.#{key}" if with_table_name
53
- return key.to_s # key may be symbol if specify foreign_key in association options
54
- end
55
-
56
- # ----------------------------------------------------------------
57
- # Contruction OPs
58
- # ----------------------------------------------------------------
59
-
60
- private
61
-
62
- def add_need_column(column)
63
- @need_columns << column.to_s
64
- end
65
-
66
- def add_association(hash)
67
- hash.each do |key, value|
68
- model = (@associations[key] ||= Model.new(get_reflect(key).klass.where(''), key, self))
69
- model.add(value)
70
- end
71
- end
72
-
73
- public
74
-
75
- def add(args)
76
- return self if args == nil
77
- args = [args] if not args.is_a?(Array)
78
- args.each do |arg|
79
- case arg
80
- when Hash ; add_association(arg)
81
- else ; add_need_column(arg)
82
- end
83
- end
84
- return self
85
- end
86
-
87
- # ----------------------------------------------------------------
88
- # Load
89
- # ----------------------------------------------------------------
90
- private
91
-
92
- def do_query(parent, reflect, relation)
93
- parent_key = get_foreign_key(reflect)
94
- relation_key = get_foreign_key(reflect, reverse: true, with_table_name: true)
95
- ids = parent.map{|s| s[parent_key]}
96
- ids.uniq!
97
- ids.compact!
98
- relation = with_conditions(reflect, relation)
99
- query = { relation_key => ids }
100
- query[reflect.type] = reflect.active_record.to_s if reflect.type
101
- return relation.joins(get_join_table(reflect)).where(query)
102
- end
103
-
104
- def set_includes_data(parent, column_name, model)
105
- reflect = get_reflect(column_name)
106
- reverse = !reflect.belongs_to?
107
- foreign_key = get_foreign_key(reflect, reverse: reverse)
108
- primary_key = get_primary_key(reflect)
109
- children = model.load_data{|relation| do_query(parent, reflect, relation) }
110
- # reverse = false: Child.where(:id => parent.pluck(:child_id))
111
- # reverse = true : Child.where(:parent_id => parent.pluck(:id))
112
- return DataCombiner.combine_data(
113
- parent,
114
- children,
115
- primary_key,
116
- column_name,
117
- foreign_key,
118
- reverse,
119
- reflect.collection?,
120
- )
121
- end
122
-
123
- def get_query_columns
124
- if @parent_model
125
- parent_reflect = @parent_model.get_reflect(@parent_association_key)
126
- prev_need_columns = @parent_model.get_foreign_key(parent_reflect, reverse: true, with_table_name: true)
127
- end
128
- next_need_columns = @associations.map{|key, _| get_foreign_key(get_reflect(key), with_table_name: true) }.uniq
129
- return [*prev_need_columns, *next_need_columns, *@need_columns].uniq(&Helper::TO_KEY_PROC)
130
- end
131
-
132
- public
133
-
134
- def load_data
135
- columns = get_query_columns
136
- key_columns = columns.map(&Helper::TO_KEY_PROC)
137
- @relation = yield(@relation) if block_given?
138
- @data = @preloaded_model ? [@preloaded_model.get_hash_data(key_columns)] : @relation.pluck_all(*columns)
139
- if @data.size != 0
140
- # for delete_extra_column_data!
141
- @extra_columns = key_columns - @need_columns.map(&Helper::TO_KEY_PROC)
142
- @associations.each do |key, model|
143
- set_includes_data(@data, key, model)
144
- end
145
- end
146
- return @data
147
- end
148
-
149
- def load_all
150
- load_data
151
- delete_extra_column_data!
152
- return @data
153
- end
154
-
155
- def delete_extra_column_data!
156
- return if @data.blank?
157
- @data.each{|s| s.except!(*@extra_columns) }
158
- @associations.each{|_, model| model.delete_extra_column_data! }
159
- end
160
-
161
- # ----------------------------------------------------------------
162
- # ● Helper methods
163
- # ----------------------------------------------------------------
164
- module Helper
165
- TO_KEY_PROC = proc{|s| Helper.column_to_key(s) }
166
- def self.column_to_key(key) # user_achievements.user_id => user_id
167
- key = key[/(\w+)[^\w]*\z/]
168
- key.gsub!(/[^\w]+/, '')
169
- return key
170
- end
171
- end
172
- end
173
- 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