deep_pluck 1.1.4 → 1.1.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7bbd3c0df3b323529a252d7d56fa596b4f85923b06b54a04560826b2793b8fe
4
- data.tar.gz: 1261968870077b5cb7f4db0c56e1045f513b6caa2a76c028a9235535635704c5
3
+ metadata.gz: 542e1ba086c4b2a8df7fb4efbf916c3448f207824717272e67bbba581154b3dc
4
+ data.tar.gz: 6710c826f3ae127d4530863b690541fe048ac6f8b3fc54a18e2da8ecd8629ee4
5
5
  SHA512:
6
- metadata.gz: dd9196070fb6189bc2e2b8da44976fd0ecd5dead243075a8abe66ae524af674d1f8e99f74abb0d2337bf65e42f85688ef20ba07c4b422f0179750b50d35421d3
7
- data.tar.gz: 34302f6ca57406fa7c61635fd0e2465cafef8b41da426b62f23fee1635df506a9d1750b2506653b0d307d2d1a599a83d23986ad9efc772c2ad66dd5792bdbef1
6
+ metadata.gz: c8c17c52a55de2a7de18ecf762adefe017f05cef401f4e0098d8ce0b5699898ac97918cbede23f802ed8ff4709c1b5655938d64c3511e72494e9777a2ec98e5e
7
+ data.tar.gz: 21aae7d86d41d53d4b05824ba13a5b7e9c7486ae9f83552146891f62397d8b03e5fc7b6b99c71c60837782d608767a8054e4674af72c2c0fbd655a1e324627a1
@@ -0,0 +1,12 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: [khiav223577] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
+ patreon: # Replace with a single Patreon username
5
+ open_collective: # Replace with a single Open Collective username
6
+ ko_fi: # Replace with a single Ko-fi username
7
+ tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
+ community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
+ liberapay: # Replace with a single Liberapay username
10
+ issuehunt: # Replace with a single IssueHunt username
11
+ otechie: # Replace with a single Otechie username
12
+ custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  /Gemfile.lock
4
+ /gemfiles/*.gemfile.lock
4
5
  /_yardoc/
5
6
  /coverage/
6
7
  /doc/
@@ -1,3 +1,7 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rails
4
+
1
5
  AllCops:
2
6
  DisabledByDefault: true
3
7
  Exclude: []
@@ -73,10 +77,6 @@ Layout/EndAlignment:
73
77
  Description: 'Align ends correctly.'
74
78
  Enabled: true
75
79
 
76
- Lint/EndInMethod:
77
- Description: 'END blocks should not be placed inside method definitions.'
78
- Enabled: true
79
-
80
80
  Lint/EnsureReturn:
81
81
  Description: 'Do not use return in an ensure block.'
82
82
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure'
@@ -90,7 +90,7 @@ Lint/FormatParameterMismatch:
90
90
  Description: 'The number of parameters to format/sprint must match the fields.'
91
91
  Enabled: true
92
92
 
93
- Lint/HandleExceptions:
93
+ Lint/SuppressedException:
94
94
  Description: "Don't suppress exception."
95
95
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions'
96
96
  Enabled: true
@@ -139,7 +139,7 @@ Lint/ShadowingOuterLocalVariable:
139
139
  for block arguments or block local variables.
140
140
  Enabled: true
141
141
 
142
- Lint/StringConversionInInterpolation:
142
+ Lint/RedundantStringCoercion:
143
143
  Description: 'Checks for Object#to_s usage in string interpolation.'
144
144
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s'
145
145
  Enabled: true
@@ -148,7 +148,7 @@ Lint/UnderscorePrefixedVariableName:
148
148
  Description: 'Do not use prefix `_` for a variable that is used.'
149
149
  Enabled: true
150
150
 
151
- Lint/UnneededCopDisableDirective:
151
+ Lint/RedundantCopDisableDirective:
152
152
  Description: >-
153
153
  Checks for rubocop:disable comments that can be removed.
154
154
  Note: this cop is not disabled when disabling all cops.
@@ -178,8 +178,8 @@ Lint/UselessAssignment:
178
178
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars'
179
179
  Enabled: true
180
180
 
181
- Lint/UselessComparison:
182
- Description: 'Checks for comparison of something with itself.'
181
+ Lint/BinaryOperatorWithIdenticalOperands:
182
+ Description: 'This cop checks for places where binary operator has identical operands.'
183
183
  Enabled: true
184
184
 
185
185
  Lint/UselessElseWithoutRescue:
@@ -221,7 +221,7 @@ Metrics/CyclomaticComplexity:
221
221
  of test cases needed to validate a method.
222
222
  Enabled: true
223
223
 
224
- Metrics/LineLength:
224
+ Layout/LineLength:
225
225
  Description: 'Limit lines to 120 characters.'
226
226
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
227
227
  Max: 120
@@ -285,7 +285,7 @@ Performance/ReverseEach:
285
285
  Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code'
286
286
  Enabled: true
287
287
 
288
- Performance/Sample:
288
+ Style/Sample:
289
289
  Description: >-
290
290
  Use `sample` instead of `shuffle.first`,
291
291
  `shuffle.last`, and `shuffle[Fixnum]`.
@@ -353,7 +353,7 @@ Rails/TimeZone:
353
353
  Description: 'Checks the correct usage of time zone aware methods.'
354
354
  StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time'
355
355
  Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
356
- Enabled: true
356
+ Enabled: false
357
357
 
358
358
  Rails/Validation:
359
359
  Description: 'Use validates :attribute, hash of validations.'
@@ -375,21 +375,21 @@ Style/Alias:
375
375
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method'
376
376
  Enabled: true
377
377
 
378
- Layout/AlignArray:
378
+ Layout/ArrayAlignment:
379
379
  Description: >-
380
380
  Align the elements of an array literal if they span more than
381
381
  one line.
382
382
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays'
383
383
  Enabled: true
384
384
 
385
- Layout/AlignHash:
385
+ Layout/HashAlignment:
386
386
  Description: >-
387
387
  Align the elements of a hash literal if they span more than
388
388
  one line.
389
389
  EnforcedHashRocketStyle: table
390
390
  Enabled: true
391
391
 
392
- Layout/AlignParameters:
392
+ Layout/ParameterAlignment:
393
393
  Description: >-
394
394
  Align the parameters of a method call if they span more
395
395
  than one line.
@@ -448,10 +448,6 @@ Style/BlockDelimiters:
448
448
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks'
449
449
  Enabled: true
450
450
 
451
- Style/BracesAroundHashParameters:
452
- Description: 'Enforce braces style around hash parameters.'
453
- Enabled: true
454
-
455
451
  Style/CaseEquality:
456
452
  Description: 'Avoid explicit use of the case equality operator(===).'
457
453
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality'
@@ -596,7 +592,7 @@ Style/EndBlock:
596
592
  Layout/EndOfLine:
597
593
  Description: 'Use Unix-style line endings.'
598
594
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf'
599
- Enabled: true
595
+ Enabled: false
600
596
 
601
597
  Style/EvenOdd:
602
598
  Description: 'Favor the use of Fixnum#even? && Fixnum#odd?'
@@ -617,14 +613,14 @@ Layout/InitialIndentation:
617
613
  Checks the indentation of the first non-blank non-comment line in a file.
618
614
  Enabled: true
619
615
 
620
- Layout/IndentFirstArgument:
616
+ Layout/FirstArgumentIndentation:
621
617
  Description: 'Checks the indentation of the first parameter in a method call.'
622
618
  Enabled: true
623
619
 
624
- Style/FlipFlop:
620
+ Lint/FlipFlop:
625
621
  Description: 'Checks for flip flops'
626
622
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops'
627
- Enabled: true
623
+ Enabled: false
628
624
 
629
625
  Style/For:
630
626
  Description: 'Checks use of for or each in multiline loops.'
@@ -634,9 +630,12 @@ Style/For:
634
630
  Style/FormatString:
635
631
  Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.'
636
632
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf'
637
- EnforcedStyle: sprintf
633
+ EnforcedStyle: percent
638
634
  Enabled: true
639
635
 
636
+ Style/FormatStringToken:
637
+ Enabled: false
638
+
640
639
  Style/GlobalVars:
641
640
  Description: 'Do not introduce global variables.'
642
641
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars'
@@ -676,14 +675,14 @@ Layout/IndentationWidth:
676
675
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
677
676
  Enabled: true
678
677
 
679
- Layout/IndentFirstArrayElement:
678
+ Layout/FirstArrayElementIndentation:
680
679
  Description: >-
681
680
  Checks the indentation of the first element in an array
682
681
  literal.
683
682
  EnforcedStyle: consistent
684
683
  Enabled: true
685
684
 
686
- Layout/IndentFirstHashElement:
685
+ Layout/FirstHashElementIndentation:
687
686
  Description: 'Checks the indentation of the first key in a hash literal.'
688
687
  EnforcedStyle: consistent
689
688
  Enabled: true
@@ -862,7 +861,7 @@ Style/PercentQLiterals:
862
861
  Style/PerlBackrefs:
863
862
  Description: 'Avoid Perl-style regex back references.'
864
863
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers'
865
- Enabled: true
864
+ Enabled: false
866
865
 
867
866
  Naming/PredicateName:
868
867
  Description: 'Check the names of predicate methods.'
@@ -940,11 +939,11 @@ Style/SingleLineBlockParams:
940
939
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks'
941
940
  Methods:
942
941
  - reduce:
943
- - acc
944
- - elem
942
+ - sum
943
+ - v
945
944
  - inject:
946
- - acc
947
- - elem
945
+ - sum
946
+ - v
948
947
  Enabled: true
949
948
 
950
949
  Style/SingleLineMethods:
@@ -1118,12 +1117,12 @@ Style/SymbolProc:
1118
1117
  Description: 'Use symbols as procs instead of blocks when possible.'
1119
1118
  Enabled: true
1120
1119
 
1121
- Layout/Tab:
1120
+ Layout/IndentationStyle:
1122
1121
  Description: 'No hard tabs.'
1123
1122
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation'
1124
1123
  Enabled: true
1125
1124
 
1126
- Layout/TrailingBlankLines:
1125
+ Layout/TrailingEmptyLines:
1127
1126
  Description: 'Checks trailing blank lines and final newline.'
1128
1127
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof'
1129
1128
  Enabled: true
@@ -1178,11 +1177,11 @@ Style/UnlessElse:
1178
1177
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless'
1179
1178
  Enabled: true
1180
1179
 
1181
- Style/UnneededCapitalW:
1180
+ Style/RedundantCapitalW:
1182
1181
  Description: 'Checks for %W when interpolation is not needed.'
1183
1182
  Enabled: true
1184
1183
 
1185
- Style/UnneededPercentQ:
1184
+ Style/RedundantPercentQ:
1186
1185
  Description: 'Checks for %q/%Q when single quotes or double quotes would do.'
1187
1186
  StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q'
1188
1187
  Enabled: true
@@ -14,6 +14,7 @@ gemfile:
14
14
  - gemfiles/5.1.gemfile
15
15
  - gemfiles/5.2.gemfile
16
16
  - gemfiles/6.0.gemfile
17
+ - gemfiles/6.1.gemfile
17
18
  matrix:
18
19
  exclude:
19
20
  - gemfile: gemfiles/3.2.gemfile
@@ -24,6 +25,8 @@ matrix:
24
25
  rvm: 2.7
25
26
  - gemfile: gemfiles/6.0.gemfile
26
27
  rvm: 2.2
28
+ - gemfile: gemfiles/6.1.gemfile
29
+ rvm: 2.2
27
30
  before_install:
28
31
  - if `ruby -e 'exit(RUBY_VERSION.to_f < 2.7)'`; then
29
32
  gem i rubygems-update -v '< 3' && update_rubygems;
@@ -1,5 +1,9 @@
1
1
  ## Change Log
2
2
 
3
+ ### [v1.1.4](https://github.com/khiav223577/deep_pluck/compare/v1.1.3...v1.1.4) 2020/01/13
4
+ - [#36](https://github.com/khiav223577/deep_pluck/pull/36) A workaround to fix mismatched association named. (@khiav223577)
5
+ - [#35](https://github.com/khiav223577/deep_pluck/pull/35) Support Ruby 2.7 (@khiav223577)
6
+
3
7
  ### [v1.1.3](https://github.com/khiav223577/deep_pluck/compare/v1.1.2...v1.1.3) 2019/12/17
4
8
  - [#34](https://github.com/khiav223577/deep_pluck/pull/34) Support for plucking directly on a has_one through association (@khiav223577)
5
9
 
@@ -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'
8
+ gem 'sqlite3', '~> 1.4.1'
9
+ end
10
+
11
+ gemspec path: '../'
@@ -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,225 +1,225 @@
1
- require 'rails_compatibility'
2
- require 'rails_compatibility/unscope_where'
3
- require 'deep_pluck/data_combiner'
4
-
5
- module DeepPluck
6
- class Model
7
- # ----------------------------------------------------------------
8
- # ● Initialize
9
- # ----------------------------------------------------------------
10
- def initialize(relation, parent_association_key = nil, parent_model = nil, need_columns: [])
11
- if relation.is_a?(ActiveRecord::Base)
12
- @model = relation
13
- @relation = nil
14
- @klass = @model.class
15
- else
16
- @model = nil
17
- @relation = relation
18
- @klass = @relation.klass
19
- end
20
-
21
- @parent_association_key = parent_association_key
22
- @parent_model = parent_model
23
- @need_columns = need_columns
24
- @associations = {}
25
- end
26
-
27
- # ----------------------------------------------------------------
28
- # ● Reader
29
- # ----------------------------------------------------------------
30
- def get_reflect(association_key)
31
- @klass.reflect_on_association(association_key.to_sym) || # add to_sym since rails 3 only support symbol
32
- fail(ActiveRecord::ConfigurationError, "ActiveRecord::ConfigurationError: Association named \
33
- '#{association_key}' was not found on #{@klass.name}; perhaps you misspelled it?"
34
- )
35
- end
36
-
37
- def with_conditions(reflect, relation)
38
- options = reflect.options
39
- relation = relation.instance_exec(&reflect.scope) if reflect.respond_to?(:scope) and reflect.scope
40
- relation = relation.where(options[:conditions]) if options[:conditions]
41
- return relation
42
- end
43
-
44
- def get_join_table(reflect)
45
- options = reflect.options
46
- return options[:through] if options[:through]
47
- return (options[:join_table] || reflect.send(:derive_join_table)) if reflect.macro == :has_and_belongs_to_many
48
- return nil
49
- end
50
-
51
- def get_primary_key(reflect)
52
- return (reflect.belongs_to? ? reflect.klass : reflect.active_record).primary_key
53
- end
54
-
55
- def get_foreign_key(reflect, reverse: false, with_table_name: false)
56
- reflect = reflect.chain.last
57
- if reverse and (table_name = get_join_table(reflect)) # reverse = parent
58
- key = reflect.chain.last.foreign_key
59
- else
60
- key = (reflect.belongs_to? == reverse ? get_primary_key(reflect) : reflect.foreign_key)
61
- table_name = (reverse ? reflect.klass : reflect.active_record).table_name
62
- end
63
- return "#{table_name}.#{key}" if with_table_name
64
- return key.to_s # key may be symbol if specify foreign_key in association options
65
- end
66
-
67
- def get_association_scope(reflect)
68
- RailsCompatibility.unscope_where(reflect.association_class.new({}, reflect).send(:association_scope))
69
- end
70
-
71
- def use_association_to_query?(reflect)
72
- reflect.through_reflection && reflect.chain.first.macro == :has_one
73
- end
74
-
75
- # ----------------------------------------------------------------
76
- # ● Contruction OPs
77
- # ----------------------------------------------------------------
78
-
79
- private
80
-
81
- def add_need_column(column)
82
- @need_columns << column
83
- end
84
-
85
- def add_association(hash)
86
- hash.each do |key, value|
87
- model = (@associations[key] ||= Model.new(get_reflect(key).klass.where(''), key, self))
88
- model.add(value)
89
- end
90
- end
91
-
92
- public
93
-
94
- def add(args)
95
- return self if args == nil
96
- args = [args] if not args.is_a?(Array)
97
- args.each do |arg|
98
- case arg
99
- when Hash ; add_association(arg)
100
- else ; add_need_column(arg)
101
- end
102
- end
103
- return self
104
- end
105
-
106
- # ----------------------------------------------------------------
107
- # ● Load
108
- # ----------------------------------------------------------------
109
- private
110
-
111
- def do_query(parent, reflect, relation)
112
- parent_key = get_foreign_key(reflect)
113
- relation_key = get_foreign_key(reflect, reverse: true, with_table_name: true)
114
- ids = parent.map{|s| s[parent_key] }
115
- ids.uniq!
116
- ids.compact!
117
- relation = with_conditions(reflect, relation)
118
- query = { relation_key => ids }
119
- query[reflect.type] = reflect.active_record.to_s if reflect.type
120
-
121
- return get_association_scope(reflect).where(query) if use_association_to_query?(reflect)
122
-
123
- join_table = get_join_table(reflect)
124
- join_table = backtrace_possible_association(relation, join_table)
125
-
126
- return relation.joins(join_table).where(query)
127
- end
128
-
129
- # Let city has_many :users, through: :schools
130
- # And the query is: City.deep_pluck('users' => :name)
131
- # We want to get the users data via `User.joins(:school).where(city_id: city_ids)`
132
- # But get_join_table(reflect) returns `:schools` not :school
133
- # No idea how to get the right association, so we try singularize or pluralize it.
134
- def backtrace_possible_association(relation, join_table)
135
- return join_table if relation.reflect_on_association(join_table)
136
- join_table.to_s.singularize.to_sym.tap{|s| return s if relation.reflect_on_association(s) }
137
- join_table.to_s.pluralize.to_sym.tap{|s| return s if relation.reflect_on_association(s) }
138
- return nil
139
- end
140
-
141
- def set_includes_data(parent, column_name, model)
142
- reflect = get_reflect(column_name)
143
- reverse = !reflect.belongs_to?
144
- foreign_key = get_foreign_key(reflect, reverse: reverse)
145
- primary_key = get_foreign_key(reflect, reverse: !reverse)
146
- children = model.load_data{|relation| do_query(parent, reflect, relation) }
147
- # reverse = false: Child.where(:id => parent.pluck(:child_id))
148
- # reverse = true : Child.where(:parent_id => parent.pluck(:id))
149
- return DataCombiner.combine_data(
150
- parent,
151
- children,
152
- primary_key,
153
- column_name,
154
- foreign_key,
155
- reverse,
156
- reflect.collection?,
157
- )
158
- end
159
-
160
- def get_query_columns
161
- if @parent_model
162
- parent_reflect = @parent_model.get_reflect(@parent_association_key)
163
- prev_need_columns = @parent_model.get_foreign_key(parent_reflect, reverse: true, with_table_name: true)
164
- end
165
- next_need_columns = @associations.map{|key, _| get_foreign_key(get_reflect(key), with_table_name: true) }.uniq
166
- return [*prev_need_columns, *next_need_columns, *@need_columns].uniq(&Helper::TO_KEY_PROC)
167
- end
168
-
169
- def pluck_values(columns)
170
- includes_values = @relation.includes_values
171
- @relation.includes_values = []
172
-
173
- result = @relation.pluck_all(*columns)
174
-
175
- @relation.includes_values = includes_values
176
- return result
177
- end
178
-
179
- def loaded_models
180
- return [@model] if @model
181
- return @relation if @relation.loaded
182
- end
183
-
184
- public
185
-
186
- def load_data
187
- columns = get_query_columns
188
- key_columns = columns.map(&Helper::TO_KEY_PROC)
189
- @relation = yield(@relation) if block_given?
190
- @data = loaded_models ? loaded_models.as_json(root: false, only: key_columns) : pluck_values(columns)
191
- if @data.size != 0
192
- # for delete_extra_column_data!
193
- @extra_columns = key_columns - @need_columns.map(&Helper::TO_KEY_PROC)
194
- @associations.each do |key, model|
195
- set_includes_data(@data, key, model)
196
- end
197
- end
198
- return @data
199
- end
200
-
201
- def load_all
202
- load_data
203
- delete_extra_column_data!
204
- return @data
205
- end
206
-
207
- def delete_extra_column_data!
208
- return if @data.blank?
209
- @data.each{|s| s.except!(*@extra_columns) }
210
- @associations.each{|_, model| model.delete_extra_column_data! }
211
- end
212
-
213
- # ----------------------------------------------------------------
214
- # ● Helper methods
215
- # ----------------------------------------------------------------
216
- module Helper
217
- TO_KEY_PROC = proc{|s| Helper.column_to_key(s) }
218
- def self.column_to_key(key) # user_achievements.user_id => user_id
219
- key = key[/(\w+)[^\w]*\z/]
220
- key.gsub!(/[^\w]+/, '')
221
- return key
222
- end
223
- end
224
- end
225
- end
1
+ require 'rails_compatibility'
2
+ require 'rails_compatibility/unscope_where'
3
+ require 'deep_pluck/data_combiner'
4
+
5
+ module DeepPluck
6
+ class Model
7
+ # ----------------------------------------------------------------
8
+ # ● Initialize
9
+ # ----------------------------------------------------------------
10
+ def initialize(relation, parent_association_key = nil, parent_model = nil, need_columns: [])
11
+ if relation.is_a?(ActiveRecord::Base)
12
+ @model = relation
13
+ @relation = nil
14
+ @klass = @model.class
15
+ else
16
+ @model = nil
17
+ @relation = relation
18
+ @klass = @relation.klass
19
+ end
20
+
21
+ @parent_association_key = parent_association_key
22
+ @parent_model = parent_model
23
+ @need_columns = need_columns
24
+ @associations = {}
25
+ end
26
+
27
+ # ----------------------------------------------------------------
28
+ # ● Reader
29
+ # ----------------------------------------------------------------
30
+ def get_reflect(association_key)
31
+ @klass.reflect_on_association(association_key.to_sym) || # add to_sym since rails 3 only support symbol
32
+ fail(ActiveRecord::ConfigurationError, "ActiveRecord::ConfigurationError: Association named \
33
+ '#{association_key}' was not found on #{@klass.name}; perhaps you misspelled it?"
34
+ )
35
+ end
36
+
37
+ def with_conditions(reflect, relation)
38
+ options = reflect.options
39
+ relation = relation.instance_exec(&reflect.scope) if reflect.respond_to?(:scope) and reflect.scope
40
+ relation = relation.where(options[:conditions]) if options[:conditions]
41
+ return relation
42
+ end
43
+
44
+ def get_join_table(reflect)
45
+ options = reflect.options
46
+ return options[:through] if options[:through]
47
+ return (options[:join_table] || reflect.send(:derive_join_table)) if reflect.macro == :has_and_belongs_to_many
48
+ return nil
49
+ end
50
+
51
+ def get_primary_key(reflect)
52
+ return (reflect.belongs_to? ? reflect.klass : reflect.active_record).primary_key
53
+ end
54
+
55
+ def get_foreign_key(reflect, reverse: false, with_table_name: false)
56
+ reflect = reflect.chain.last
57
+ if reverse and (table_name = get_join_table(reflect)) # reverse = parent
58
+ key = reflect.chain.last.foreign_key
59
+ else
60
+ key = (reflect.belongs_to? == reverse ? get_primary_key(reflect) : reflect.foreign_key)
61
+ table_name = (reverse ? reflect.klass : reflect.active_record).table_name
62
+ end
63
+ return "#{table_name}.#{key}" if with_table_name
64
+ return key.to_s # key may be symbol if specify foreign_key in association options
65
+ end
66
+
67
+ def get_association_scope(reflect)
68
+ RailsCompatibility.unscope_where(reflect.association_class.new({}, reflect).send(:association_scope))
69
+ end
70
+
71
+ def use_association_to_query?(reflect)
72
+ reflect.through_reflection && reflect.chain.first.macro == :has_one
73
+ end
74
+
75
+ # ----------------------------------------------------------------
76
+ # ● Contruction OPs
77
+ # ----------------------------------------------------------------
78
+
79
+ private
80
+
81
+ def add_need_column(column)
82
+ @need_columns << column
83
+ end
84
+
85
+ def add_association(hash)
86
+ hash.each do |key, value|
87
+ model = (@associations[key] ||= Model.new(get_reflect(key).klass.where(''), key, self))
88
+ model.add(value)
89
+ end
90
+ end
91
+
92
+ public
93
+
94
+ def add(args)
95
+ return self if args == nil
96
+ args = [args] if not args.is_a?(Array)
97
+ args.each do |arg|
98
+ case arg
99
+ when Hash ; add_association(arg)
100
+ else ; add_need_column(arg)
101
+ end
102
+ end
103
+ return self
104
+ end
105
+
106
+ # ----------------------------------------------------------------
107
+ # ● Load
108
+ # ----------------------------------------------------------------
109
+ private
110
+
111
+ def do_query(parent, reflect, relation)
112
+ parent_key = get_foreign_key(reflect)
113
+ relation_key = get_foreign_key(reflect, reverse: true, with_table_name: true)
114
+ ids = parent.map{|s| s[parent_key] }
115
+ ids.uniq!
116
+ ids.compact!
117
+ relation = with_conditions(reflect, relation)
118
+ query = { relation_key => ids }
119
+ query[reflect.type] = reflect.active_record.to_s if reflect.type
120
+
121
+ return get_association_scope(reflect).where(query) if use_association_to_query?(reflect)
122
+
123
+ join_table = get_join_table(reflect)
124
+ join_table = backtrace_possible_association(relation, join_table)
125
+
126
+ return relation.joins(join_table).where(query)
127
+ end
128
+
129
+ # Let city has_many :users, through: :schools
130
+ # And the query is: City.deep_pluck('users' => :name)
131
+ # We want to get the users data via `User.joins(:school).where(city_id: city_ids)`
132
+ # But get_join_table(reflect) returns `:schools` not :school
133
+ # No idea how to get the right association, so we try singularize or pluralize it.
134
+ def backtrace_possible_association(relation, join_table)
135
+ return join_table if relation.reflect_on_association(join_table)
136
+ join_table.to_s.singularize.to_sym.tap{|s| return s if relation.reflect_on_association(s) }
137
+ join_table.to_s.pluralize.to_sym.tap{|s| return s if relation.reflect_on_association(s) }
138
+ return nil
139
+ end
140
+
141
+ def set_includes_data(parent, column_name, model)
142
+ reflect = get_reflect(column_name)
143
+ reverse = !reflect.belongs_to?
144
+ foreign_key = get_foreign_key(reflect, reverse: reverse)
145
+ primary_key = get_foreign_key(reflect, reverse: !reverse)
146
+ children = model.load_data{|relation| do_query(parent, reflect, relation) }
147
+ # reverse = false: Child.where(:id => parent.pluck(:child_id))
148
+ # reverse = true : Child.where(:parent_id => parent.pluck(:id))
149
+ return DataCombiner.combine_data(
150
+ parent,
151
+ children,
152
+ primary_key,
153
+ column_name,
154
+ foreign_key,
155
+ reverse,
156
+ reflect.collection?,
157
+ )
158
+ end
159
+
160
+ def get_query_columns
161
+ if @parent_model
162
+ parent_reflect = @parent_model.get_reflect(@parent_association_key)
163
+ prev_need_columns = @parent_model.get_foreign_key(parent_reflect, reverse: true, with_table_name: true)
164
+ end
165
+ next_need_columns = @associations.map{|key, _| get_foreign_key(get_reflect(key), with_table_name: true) }.uniq
166
+ return [*prev_need_columns, *next_need_columns, *@need_columns].uniq(&Helper::TO_KEY_PROC)
167
+ end
168
+
169
+ def pluck_values(columns)
170
+ includes_values = @relation.includes_values
171
+ @relation.includes_values = []
172
+
173
+ result = @relation.pluck_all(*columns)
174
+
175
+ @relation.includes_values = includes_values
176
+ return result
177
+ end
178
+
179
+ def loaded_models
180
+ return [@model] if @model
181
+ return @relation if @relation.loaded
182
+ end
183
+
184
+ public
185
+
186
+ def load_data
187
+ columns = get_query_columns
188
+ key_columns = columns.map(&Helper::TO_KEY_PROC)
189
+ @relation = yield(@relation) if block_given?
190
+ @data = loaded_models ? loaded_models.as_json(root: false, only: key_columns) : pluck_values(columns)
191
+ if @data.size != 0
192
+ # for delete_extra_column_data!
193
+ @extra_columns = key_columns - @need_columns.map(&Helper::TO_KEY_PROC)
194
+ @associations.each do |key, model|
195
+ set_includes_data(@data, key, model)
196
+ end
197
+ end
198
+ return @data
199
+ end
200
+
201
+ def load_all
202
+ load_data
203
+ delete_extra_column_data!
204
+ return @data
205
+ end
206
+
207
+ def delete_extra_column_data!
208
+ return if @data.blank?
209
+ @data.each{|s| s.except!(*@extra_columns) }
210
+ @associations.each{|_, model| model.delete_extra_column_data! }
211
+ end
212
+
213
+ # ----------------------------------------------------------------
214
+ # ● Helper methods
215
+ # ----------------------------------------------------------------
216
+ module Helper
217
+ TO_KEY_PROC = proc{|s| Helper.column_to_key(s) }
218
+ def self.column_to_key(key) # user_achievements.user_id => user_id
219
+ key = key[/(\w+)[^\w]*\z/]
220
+ key.gsub!(/[^\w]+/, '')
221
+ return key
222
+ end
223
+ end
224
+ end
225
+ end
@@ -1,3 +1,3 @@
1
1
  module DeepPluck
2
- VERSION = '1.1.4'
2
+ VERSION = '1.1.5'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deep_pluck
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 1.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - khiav reoy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-13 00:00:00.000000000 Z
11
+ date: 2021-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -123,6 +123,7 @@ executables: []
123
123
  extensions: []
124
124
  extra_rdoc_files: []
125
125
  files:
126
+ - ".github/FUNDING.yml"
126
127
  - ".gitignore"
127
128
  - ".rubocop.yml"
128
129
  - ".travis.yml"
@@ -140,6 +141,7 @@ files:
140
141
  - gemfiles/5.1.gemfile
141
142
  - gemfiles/5.2.gemfile
142
143
  - gemfiles/6.0.gemfile
144
+ - gemfiles/6.1.gemfile
143
145
  - lib/deep_pluck.rb
144
146
  - lib/deep_pluck/data_combiner.rb
145
147
  - lib/deep_pluck/model.rb