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