eager_group 0.8.2 → 0.9.0

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: 5370e57578e725513b391777f1a86e3b1d7560bc596a08b005110d3220d43325
4
- data.tar.gz: b4987065f8781190983f2b92a84db5ea7e248f43290763ba483d52ec472cb1e8
3
+ metadata.gz: c8bad88cf4f0de57942e0477ee107ce8f40eeeded25b37def08ef560907ef143
4
+ data.tar.gz: f14470d5456dbce04be8fac648623fbc0e1dbb25a3e89af5ebe7bde9dd6a61d5
5
5
  SHA512:
6
- metadata.gz: 172290ed5e79c34139cdc35976f22e612e225e39f6b417f07f86696970f28969f058c59189e683d9ca299d5d61643dbdbf8220648889f7e4bbcb66cb30f68d7e
7
- data.tar.gz: 91577037162ebb062b8939c02fc2aa66e4fdf1e891fb6e20ffd199d7e80879c38f20ae4fc4cc95ae32c7f16ab90377816ed42dc215dd0cd92fad9ce01f9450ea
6
+ metadata.gz: 6e026cf0d9f8a9839b498b995ba7d48ab24afe2dc48f3e7d41cba16069ac4bc9b3c15d202e7b9730859aa81f24c8cb77a9e2bc8572e720a58457cc7f89f171a0
7
+ data.tar.gz: 0033033e6b427479d0a49c2e2e1b1149f40a63a46f12aa42aa49f54fb91b27f84ee045aa96014c37b745fbad9b5ca84fab20b014ef521e478cbce058f681f803
@@ -0,0 +1,18 @@
1
+ name: test
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ jobs:
7
+ test:
8
+ name: test
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - name: Set up Ruby
13
+ uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: 3.1
16
+ bundler-cache: true
17
+ - name: Run tests
18
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .idea
11
+ .tool-versions
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Next Release
2
2
 
3
+ ## 0.9.0 (12/04/2022)
4
+
5
+ * Support `has_many` through `belongs_to`
6
+
3
7
  ## 0.8.2 (09/22/2022)
4
8
 
5
9
  * Add MIT-LICENSE
data/eager_group.gemspec CHANGED
@@ -29,4 +29,5 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency 'rake', '~> 10.0'
30
30
  spec.add_development_dependency 'rspec', '~> 3.3'
31
31
  spec.add_development_dependency 'sqlite3'
32
+ spec.add_development_dependency 'pry'
32
33
  end
@@ -9,12 +9,13 @@ module ActiveRecord
9
9
  end
10
10
 
11
11
  def eager_group(*args)
12
- check_if_method_has_arguments!('eager_group', args)
12
+ check_if_method_has_arguments!(__callee__, args)
13
+
13
14
  spawn.eager_group!(*args)
14
15
  end
15
16
 
16
17
  def eager_group!(*args)
17
- self.eager_group_values += args
18
+ self.eager_group_values |= args
18
19
  self
19
20
  end
20
21
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerGroup
4
+ class Preloader
5
+ class AggregationFinder
6
+ attr_reader :klass, :reflection, :definition, :arguments, :record_ids
7
+
8
+ def initialize(klass, definition, arguments, records)
9
+ @klass = klass
10
+ @definition = definition
11
+ @reflection = @klass.reflect_on_association(definition.association)
12
+ @arguments = arguments
13
+ @records = records
14
+ end
15
+
16
+ def definition_scope
17
+ reflection.klass.instance_exec(*arguments, &definition.scope) if definition.scope
18
+ end
19
+
20
+ def record_ids
21
+ @record_ids ||= @records.map { |record| record.send(group_by_key) }
22
+ end
23
+
24
+ def group_by_key
25
+ @klass.primary_key
26
+ end
27
+
28
+ private
29
+
30
+ def polymophic_as_condition
31
+ reflection.type ? { reflection.name => { reflection.type => @klass.base_class.name } } : []
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerGroup
4
+ class Preloader
5
+ class HasMany < AggregationFinder
6
+ def group_by_foreign_key
7
+ reflection.foreign_key
8
+ end
9
+
10
+ def aggregate_hash
11
+ scope = reflection.klass.all.tap{|query| query.merge!(definition_scope) if definition_scope }
12
+
13
+ scope.where(group_by_foreign_key => record_ids).
14
+ where(polymophic_as_condition).
15
+ group(group_by_foreign_key).
16
+ send(definition.aggregation_function, definition.column_name)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerGroup
4
+ class Preloader
5
+ class HasManyThroughBelongsTo < AggregationFinder
6
+ def group_by_foreign_key
7
+ "#{reflection.table_name}.#{reflection.through_reflection.klass.reflect_on_association(reflection.name).foreign_key}"
8
+ end
9
+
10
+ def aggregate_hash
11
+ scope = reflection.klass.all.tap{|query| query.merge!(definition_scope) if definition_scope }
12
+
13
+ scope.where(group_by_foreign_key => record_ids).
14
+ where(polymophic_as_condition).
15
+ group(group_by_foreign_key).
16
+ send(definition.aggregation_function, definition.column_name)
17
+ end
18
+
19
+ def group_by_key
20
+ reflection.through_reflection.foreign_key
21
+ end
22
+
23
+ def polymophic_as_condition
24
+ reflection.type ? { reflection.name => { reflection.type => reflection.through_reflection.klass.base_class.name } } : []
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerGroup
4
+ class Preloader
5
+ class HasManyThroughMany < AggregationFinder
6
+ def group_by_foreign_key
7
+ "#{reflection.through_reflection.name}.#{reflection.through_reflection.foreign_key}"
8
+ end
9
+
10
+ def aggregate_hash
11
+ scope = klass.joins(reflection.name).tap{|query| query.merge!(definition_scope) if definition_scope }
12
+
13
+ scope.where(group_by_foreign_key => record_ids).
14
+ where(polymophic_as_condition).
15
+ group(group_by_foreign_key).
16
+ send(definition.aggregation_function, definition.column_name)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerGroup
4
+ class Preloader
5
+ class ManyToMany < AggregationFinder
6
+ def group_by_foreign_key
7
+ "#{reflection.join_table}.#{reflection.foreign_key}"
8
+ end
9
+
10
+ def aggregate_hash
11
+ scope = klass.joins(reflection.name).tap{|query| query.merge!(definition_scope) if definition_scope}
12
+
13
+ scope.where(group_by_foreign_key => record_ids).
14
+ where(polymophic_as_condition).
15
+ group(group_by_foreign_key).
16
+ send(definition.aggregation_function, definition.column_name)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -2,15 +2,21 @@
2
2
 
3
3
  module EagerGroup
4
4
  class Preloader
5
+ autoload :AggregationFinder, 'eager_group/preloader/aggregation_finder'
6
+ autoload :HasMany, 'eager_group/preloader/has_many'
7
+ autoload :HasManyThroughBelongsTo, 'eager_group/preloader/has_many_through_belongs_to'
8
+ autoload :HasManyThroughMany, 'eager_group/preloader/has_many_through_many'
9
+ autoload :ManyToMany, 'eager_group/preloader/many_to_many'
10
+
5
11
  def initialize(klass, records, eager_group_values)
6
12
  @klass = klass
7
13
  @records = Array.wrap(records).compact.uniq
8
- @eager_group_values = eager_group_values
14
+ eager_group_definitions = @klass.eager_group_definitions
15
+ @eager_group_values = eager_group_values.all? { |value| eager_group_definitions.key?(value) } ? eager_group_values : [eager_group_values]
9
16
  end
10
17
 
11
18
  # Preload aggregate functions
12
19
  def run
13
- primary_key = @klass.primary_key
14
20
  @eager_group_values.each do |eager_group_value|
15
21
  definition_key, arguments =
16
22
  eager_group_value.is_a?(Array) ? [eager_group_value.shift, eager_group_value] : [eager_group_value, nil]
@@ -22,37 +28,44 @@ module EagerGroup
22
28
 
23
29
  @klass = @records.first.class
24
30
  end
25
- record_ids = @records.map { |record| record.send(primary_key) }
26
- unless definition = @klass.eager_group_definitions[definition_key]
27
- next
31
+
32
+ Array.wrap(definition_key).each do |key|
33
+ find_aggregate_values_per_definition!(key, arguments)
28
34
  end
35
+ end
36
+ end
29
37
 
30
- reflection = @klass.reflect_on_association(definition.association)
31
- association_class = reflection.klass
32
- association_class = association_class.instance_exec(*arguments, &definition.scope) if definition.scope
38
+ def find_aggregate_values_per_definition!(definition_key, arguments)
39
+ unless definition = @klass.eager_group_definitions[definition_key]
40
+ return
41
+ end
33
42
 
34
- foreign_key, aggregate_hash =
35
- if reflection.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
36
- ["#{reflection.join_table}.#{reflection.foreign_key}", @klass.joins(reflection.name)]
37
- elsif reflection.through_reflection
38
- [
39
- "#{reflection.through_reflection.name}.#{reflection.through_reflection.foreign_key}",
40
- @klass.joins(reflection.name)
41
- ]
42
- else
43
- [reflection.foreign_key, association_class]
44
- end
45
- aggregate_hash =
46
- aggregate_hash.where(foreign_key => record_ids).where(polymophic_as_condition(reflection)).group(foreign_key)
47
- .send(definition.aggregation_function, definition.column_name)
48
- if definition.need_load_object
49
- aggregate_objects = reflection.klass.find(aggregate_hash.values).each_with_object({}) { |o, h| h[o.id] = o }
50
- aggregate_hash.keys.each { |key| aggregate_hash[key] = aggregate_objects[aggregate_hash[key]] }
51
- end
52
- @records.each do |record|
53
- id = record.send(primary_key)
54
- record.send("#{definition_key}=", aggregate_hash[id] || definition.default_value)
43
+ reflection = @klass.reflect_on_association(definition.association)
44
+ return if reflection.blank?
45
+
46
+ aggregation_finder_class = if reflection.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
47
+ ManyToMany
48
+ elsif reflection.through_reflection
49
+ if reflection.through_reflection.is_a?(ActiveRecord::Reflection::BelongsToReflection)
50
+ HasManyThroughBelongsTo
51
+ else
52
+ HasManyThroughMany
55
53
  end
54
+ else
55
+ HasMany
56
+ end
57
+
58
+ aggregation_finder = aggregation_finder_class.new(@klass, definition, arguments, @records)
59
+ aggregate_hash = aggregation_finder.aggregate_hash
60
+
61
+ if definition.need_load_object
62
+ aggregate_objects = reflection.klass.find(aggregate_hash.values).each_with_object({}) { |o, h| h[o.id] = o }
63
+ aggregate_hash.keys.each { |key| aggregate_hash[key] = aggregate_objects[aggregate_hash[key]] }
64
+ end
65
+
66
+ @records.each do |record|
67
+ id = record.send(aggregation_finder.group_by_key)
68
+ record.send("#{definition_key}=", aggregate_hash[id] || definition.default_value)
56
69
  end
57
70
  end
58
71
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerGroup
4
- VERSION = '0.8.2'
4
+ VERSION = '0.9.0'
5
5
  end
data/lib/eager_group.rb CHANGED
@@ -40,7 +40,7 @@ module EagerGroup
40
40
  private
41
41
 
42
42
  def preload_eager_group(*eager_group_value)
43
- EagerGroup::Preloader.new(self.class, [self], [eager_group_value]).run
43
+ EagerGroup::Preloader.new(self.class, [self], eager_group_value).run
44
44
  end
45
45
  end
46
46
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eager_group
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-22 00:00:00.000000000 Z
11
+ date: 2022-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  description: Fix n+1 aggregate sql functions for rails
126
140
  email:
127
141
  - flyerhzm@gmail.com
@@ -129,6 +143,7 @@ executables: []
129
143
  extensions: []
130
144
  extra_rdoc_files: []
131
145
  files:
146
+ - ".github/workflows/main.yml"
132
147
  - ".gitignore"
133
148
  - ".rspec"
134
149
  - ".travis.yml"
@@ -145,6 +160,11 @@ files:
145
160
  - lib/eager_group.rb
146
161
  - lib/eager_group/definition.rb
147
162
  - lib/eager_group/preloader.rb
163
+ - lib/eager_group/preloader/aggregation_finder.rb
164
+ - lib/eager_group/preloader/has_many.rb
165
+ - lib/eager_group/preloader/has_many_through_belongs_to.rb
166
+ - lib/eager_group/preloader/has_many_through_many.rb
167
+ - lib/eager_group/preloader/many_to_many.rb
148
168
  - lib/eager_group/version.rb
149
169
  homepage: https://github.com/flyerhzm/eager_group
150
170
  licenses:
@@ -165,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
185
  - !ruby/object:Gem::Version
166
186
  version: '0'
167
187
  requirements: []
168
- rubygems_version: 3.3.7
188
+ rubygems_version: 3.3.26
169
189
  signing_key:
170
190
  specification_version: 4
171
191
  summary: Fix n+1 aggregate sql functions