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 +4 -4
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +4 -0
- data/eager_group.gemspec +1 -0
- data/lib/active_record/with_eager_group.rb +3 -2
- data/lib/eager_group/preloader/aggregation_finder.rb +35 -0
- data/lib/eager_group/preloader/has_many.rb +20 -0
- data/lib/eager_group/preloader/has_many_through_belongs_to.rb +28 -0
- data/lib/eager_group/preloader/has_many_through_many.rb +20 -0
- data/lib/eager_group/preloader/many_to_many.rb +20 -0
- data/lib/eager_group/preloader.rb +42 -29
- data/lib/eager_group/version.rb +1 -1
- data/lib/eager_group.rb +1 -1
- metadata +23 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8bad88cf4f0de57942e0477ee107ce8f40eeeded25b37def08ef560907ef143
|
4
|
+
data.tar.gz: f14470d5456dbce04be8fac648623fbc0e1dbb25a3e89af5ebe7bde9dd6a61d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/CHANGELOG.md
CHANGED
data/eager_group.gemspec
CHANGED
@@ -9,12 +9,13 @@ module ActiveRecord
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def eager_group(*args)
|
12
|
-
check_if_method_has_arguments!(
|
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
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
|
data/lib/eager_group/version.rb
CHANGED
data/lib/eager_group.rb
CHANGED
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.
|
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-
|
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.
|
188
|
+
rubygems_version: 3.3.26
|
169
189
|
signing_key:
|
170
190
|
specification_version: 4
|
171
191
|
summary: Fix n+1 aggregate sql functions
|