deep_pluck 1.1.4 → 1.1.5

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