eager_group 0.6.1 → 0.7.0
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +86 -33
- data/eager_group.gemspec +1 -1
- data/lib/eager_group/active_record_base.rb +1 -1
- data/lib/eager_group/active_record_relation.rb +1 -0
- data/lib/eager_group/definition.rb +15 -1
- data/lib/eager_group/preloader.rb +8 -3
- data/lib/eager_group/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ee556b1563f43bb0b0b19d07b47cadf828a0a06586687bb23cea618c85f4d4f
|
4
|
+
data.tar.gz: 67f6b99dae67ae911a02261c98d662fa465407579b4962132d7b7d024bed9816
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 912675975d5fab05a06045bc387ac1c128f2c313c692fc60f5df925d04bb7d846d9d1a458cc64c6a2c6139fc77e7d7c0721bf4173c3d115922ad2ffecff047e0
|
7
|
+
data.tar.gz: ce554fd38a31348d7fee55472aaefcf559a88db0f33f24a516273a83d7380079594377a33ff5d625802329496fc50d8f611006dc4b2bf3fabf00eaef8d26e835
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -8,29 +8,37 @@ flyerhzm/eager_group](https://awesomecode.io/projects/e5386790-9420-4003-831a-c9
|
|
8
8
|
|
9
9
|
Fix n+1 aggregate sql functions for rails, like
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
```
|
12
|
+
SELECT "posts".* FROM "posts";
|
13
|
+
SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."status" = 'approved'
|
14
|
+
SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 2 AND "comments"."status" = 'approved'
|
15
|
+
SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 3 AND "comments"."status" = 'approved'
|
16
|
+
```
|
15
17
|
|
16
18
|
=>
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
```
|
21
|
+
SELECT "posts".* FROM "posts";
|
22
|
+
SELECT COUNT(*) AS count_all, post_id AS post_id FROM "comments" WHERE "comments"."post_id" IN (1, 2, 3) AND "comments"."status" = 'approved' GROUP BY post_id;
|
23
|
+
```
|
20
24
|
|
21
25
|
or
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
+
```
|
28
|
+
SELECT "posts".* FROM "posts";
|
29
|
+
SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 1;
|
30
|
+
SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 2;
|
31
|
+
SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 3;
|
32
|
+
```
|
27
33
|
|
28
34
|
=>
|
29
35
|
|
30
|
-
|
31
|
-
|
36
|
+
```
|
37
|
+
SELECT "posts".* FROM "posts";
|
38
|
+
SELECT AVG("comments"."rating") AS average_comments_rating, post_id AS post_id FROM "comments" WHERE "comments"."post_id" IN (1, 2, 3) GROUP BY post_id;
|
39
|
+
```
|
32
40
|
|
33
|
-
It supports Rails 4.x and Rails
|
41
|
+
It supports Rails 4.x, Rails 5.x and Rails 6.x
|
34
42
|
|
35
43
|
## Installation
|
36
44
|
|
@@ -42,29 +50,35 @@ gem 'eager_group'
|
|
42
50
|
|
43
51
|
And then execute:
|
44
52
|
|
45
|
-
|
53
|
+
```
|
54
|
+
$ bundle
|
55
|
+
```
|
46
56
|
|
47
57
|
Or install it yourself as:
|
48
58
|
|
49
|
-
|
59
|
+
```
|
60
|
+
$ gem install eager_group
|
61
|
+
```
|
50
62
|
|
51
63
|
## Usage
|
52
64
|
|
53
65
|
First you need to define what aggregate function you want to eager
|
54
66
|
load.
|
55
67
|
|
56
|
-
|
57
|
-
|
68
|
+
```ruby
|
69
|
+
class Post < ActiveRecord::Base
|
70
|
+
has_many :comments
|
58
71
|
|
59
|
-
|
60
|
-
|
61
|
-
|
72
|
+
define_eager_group :comments_average_rating, :comments, :average, :rating
|
73
|
+
define_eager_group :approved_comments_count, :comments, :count, :*, -> { approved }
|
74
|
+
end
|
62
75
|
|
63
|
-
|
64
|
-
|
76
|
+
class Comment < ActiveRecord::Base
|
77
|
+
belongs_to :post
|
65
78
|
|
66
|
-
|
67
|
-
|
79
|
+
scope :approved, -> { where(status: 'approved') }
|
80
|
+
end
|
81
|
+
```
|
68
82
|
|
69
83
|
The parameters for `define_eager_group` are as follows
|
70
84
|
|
@@ -73,18 +87,22 @@ method, it also generates a method with the same name to fetch the
|
|
73
87
|
result.
|
74
88
|
* `association`, association name you want to aggregate.
|
75
89
|
* `aggregate_function`, aggregate sql function, can be one of `average`,
|
76
|
-
`count`, `maximum`, `minimum`, `sum
|
90
|
+
`count`, `maximum`, `minimum`, `sum`, I define 2 additional aggregate
|
91
|
+
function `first_object` and `last_object` to fetch first and last object
|
92
|
+
from associations.
|
77
93
|
* `column_name`, aggregate column name, it can be `:*` for `count`
|
78
94
|
* `scope`, scope is optional, it's used to filter data for aggregation.
|
79
95
|
|
80
96
|
Then you can use `eager_group` to fix n+1 aggregate sql functions
|
81
97
|
when querying
|
82
98
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
99
|
+
```ruby
|
100
|
+
posts = Post.all.eager_group(:comments_average_rating, :approved_comments_count)
|
101
|
+
posts.each do |post|
|
102
|
+
post.comments_average_rating
|
103
|
+
post.approved_comments_count
|
104
|
+
end
|
105
|
+
```
|
88
106
|
|
89
107
|
EagerGroup will execute `GROUP BY` sqls for you then set the value of
|
90
108
|
attributes.
|
@@ -93,16 +111,51 @@ attributes.
|
|
93
111
|
You can call the `definition_name` directly for convenience,
|
94
112
|
but it would not help you to fix n+1 aggregate sql issue.
|
95
113
|
|
96
|
-
|
97
|
-
|
98
|
-
|
114
|
+
```
|
115
|
+
post = Post.first
|
116
|
+
post.commets_average_rating
|
117
|
+
post.approved_comments_count
|
118
|
+
```
|
99
119
|
|
100
120
|
## Advanced
|
101
121
|
|
122
|
+
eager_group through association
|
123
|
+
|
102
124
|
```ruby
|
103
125
|
User.limit(10).includes(:posts).eager_group(posts: [:comments_average_rating, :approved_comments_count])
|
104
126
|
```
|
105
127
|
|
128
|
+
pass parameter to scope
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class Post < ActiveRecord::Base
|
132
|
+
has_many :comments
|
133
|
+
|
134
|
+
define_eager_group :comments_average_rating_by_author, :comments, :average, :rating, ->(author, ignore) { by_author(author, ignore) }
|
135
|
+
end
|
136
|
+
|
137
|
+
posts = Post.all.eager_group([:comments_average_rating_by_author, author, true])
|
138
|
+
posts.each { |post| post.comments_average_rating_by_author }
|
139
|
+
```
|
140
|
+
|
141
|
+
first_object and last_object aggregation to fetch first and last
|
142
|
+
association object.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
class Post < ActiveRecord::Base
|
146
|
+
has_many :comments
|
147
|
+
|
148
|
+
define_eager_group :first_comment, :comments, :first_object, :id
|
149
|
+
define_eager_group :last_comment, :comments, :last_object, :id
|
150
|
+
end
|
151
|
+
|
152
|
+
posts = Post.all.eager_group(:first_comment, :last_comment)
|
153
|
+
posts.each do |post|
|
154
|
+
post.first_comment
|
155
|
+
post.last_comment
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
106
159
|
|
107
160
|
## Benchmark
|
108
161
|
|
data/eager_group.gemspec
CHANGED
@@ -27,5 +27,5 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency 'bundler'
|
28
28
|
spec.add_development_dependency 'rake', '~> 10.0'
|
29
29
|
spec.add_development_dependency 'rspec', '~> 3.3'
|
30
|
-
spec.add_development_dependency 'sqlite3'
|
30
|
+
spec.add_development_dependency 'sqlite3'
|
31
31
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module EagerGroup
|
4
4
|
class Definition
|
5
|
-
attr_reader :association, :
|
5
|
+
attr_reader :association, :column_name, :scope
|
6
6
|
|
7
7
|
def initialize(association, aggregate_function, column_name, scope)
|
8
8
|
@association = association
|
@@ -10,5 +10,19 @@ module EagerGroup
|
|
10
10
|
@column_name = column_name
|
11
11
|
@scope = scope
|
12
12
|
end
|
13
|
+
|
14
|
+
def aggregation_function
|
15
|
+
return :maximum if @aggregate_function.to_sym == :last_object
|
16
|
+
return :minimum if @aggregate_function.to_sym == :first_object
|
17
|
+
@aggregate_function
|
18
|
+
end
|
19
|
+
|
20
|
+
def need_load_object
|
21
|
+
%i[first_object last_object].include?(@aggregate_function.to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
def default_value
|
25
|
+
%i[first_object last_object].include?(@aggregate_function.to_sym) ? nil : 0
|
26
|
+
end
|
13
27
|
end
|
14
28
|
end
|
@@ -17,6 +17,7 @@ module EagerGroup
|
|
17
17
|
association_name, definition_key = *definition_key.first
|
18
18
|
@records = @records.flat_map { |record| record.send(association_name) }
|
19
19
|
next if @records.empty?
|
20
|
+
|
20
21
|
@klass = @records.first.class
|
21
22
|
end
|
22
23
|
record_ids = @records.map { |record| record.send(primary_key) }
|
@@ -32,16 +33,20 @@ module EagerGroup
|
|
32
33
|
.where(foreign_key => record_ids)
|
33
34
|
.where(polymophic_as_condition(reflection.through_reflection))
|
34
35
|
.group(foreign_key)
|
35
|
-
.send(definition.
|
36
|
+
.send(definition.aggregation_function, definition.column_name)
|
36
37
|
else
|
37
38
|
aggregate_hash = association_class.where(reflection.foreign_key => record_ids)
|
38
39
|
.where(polymophic_as_condition(reflection))
|
39
40
|
.group(reflection.foreign_key)
|
40
|
-
.send(definition.
|
41
|
+
.send(definition.aggregation_function, definition.column_name)
|
42
|
+
end
|
43
|
+
if definition.need_load_object
|
44
|
+
aggregate_objects = association_class.find(aggregate_hash.values).each_with_object({}) { |o, h| h[o.id] = o }
|
45
|
+
aggregate_hash.keys.each { |key| aggregate_hash[key] = aggregate_objects[aggregate_hash[key]] }
|
41
46
|
end
|
42
47
|
@records.each do |record|
|
43
48
|
id = record.send(primary_key)
|
44
|
-
record.send("#{definition_key}=", aggregate_hash[id] ||
|
49
|
+
record.send("#{definition_key}=", aggregate_hash[id] || definition.default_value)
|
45
50
|
end
|
46
51
|
end
|
47
52
|
end
|
data/lib/eager_group/version.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.7.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: 2019-
|
11
|
+
date: 2019-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -98,16 +98,16 @@ dependencies:
|
|
98
98
|
name: sqlite3
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
|
-
- - "
|
101
|
+
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
103
|
+
version: '0'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
|
-
- - "
|
108
|
+
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
110
|
+
version: '0'
|
111
111
|
description: Fix n+1 aggregate sql functions for rails
|
112
112
|
email:
|
113
113
|
- flyerhzm@gmail.com
|
@@ -152,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
152
|
- !ruby/object:Gem::Version
|
153
153
|
version: '0'
|
154
154
|
requirements: []
|
155
|
-
rubygems_version: 3.0.
|
155
|
+
rubygems_version: 3.0.3
|
156
156
|
signing_key:
|
157
157
|
specification_version: 4
|
158
158
|
summary: Fix n+1 aggregate sql functions
|