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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36652bc032832f5a84dc02733286cd156a3253b8c1ba3b1b9289fec7686745d7
4
- data.tar.gz: da2a878d043e2a02abb5f64c6d1c679dd02fe02220ba6399f5312e1ee096f953
3
+ metadata.gz: 7ee556b1563f43bb0b0b19d07b47cadf828a0a06586687bb23cea618c85f4d4f
4
+ data.tar.gz: 67f6b99dae67ae911a02261c98d662fa465407579b4962132d7b7d024bed9816
5
5
  SHA512:
6
- metadata.gz: b7b4aacb136f425da49b81bf4d34c4bb467abc940b05fb936bd364b6884a322330acdec85449008de6f64716adc461ffebcad7a4b760f58fb44187b6abd2f3ba
7
- data.tar.gz: 550d2eb69a366f90227c15382e25044078731610caae901982478baef95935dd581fc3b2a3affa04c48cab92a340d84744c18aeb5cb0a865a083ea78cee7bd69
6
+ metadata.gz: 912675975d5fab05a06045bc387ac1c128f2c313c692fc60f5df925d04bb7d846d9d1a458cc64c6a2c6139fc77e7d7c0721bf4173c3d115922ad2ffecff047e0
7
+ data.tar.gz: ce554fd38a31348d7fee55472aaefcf559a88db0f33f24a516273a83d7380079594377a33ff5d625802329496fc50d8f611006dc4b2bf3fabf00eaef8d26e835
@@ -1,5 +1,9 @@
1
1
  # Next Release
2
2
 
3
+ ## 0.7.0 (03/05/2018)
4
+
5
+ * Add first_object and last_object aggregation
6
+
3
7
  ## 0.6.1 (03/05/2018)
4
8
 
5
9
  * Skip preload when association is empty
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
- SELECT "posts".* FROM "posts";
12
- SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."status" = 'approved'
13
- SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 2 AND "comments"."status" = 'approved'
14
- SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 3 AND "comments"."status" = 'approved'
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
- SELECT "posts".* FROM "posts";
19
- 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;
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
- SELECT "posts".* FROM "posts";
24
- SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 1;
25
- SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 2;
26
- SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 3;
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
- SELECT "posts".* FROM "posts";
31
- 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;
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 5.x
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
- $ bundle
53
+ ```
54
+ $ bundle
55
+ ```
46
56
 
47
57
  Or install it yourself as:
48
58
 
49
- $ gem install eager_group
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
- class Post < ActiveRecord::Base
57
- has_many :comments
68
+ ```ruby
69
+ class Post < ActiveRecord::Base
70
+ has_many :comments
58
71
 
59
- define_eager_group :comments_average_rating, :comments, :average, :rating
60
- define_eager_group :approved_comments_count, :comments, :count, :*, -> { approved }
61
- end
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
- class Comment < ActiveRecord::Base
64
- belongs_to :post
76
+ class Comment < ActiveRecord::Base
77
+ belongs_to :post
65
78
 
66
- scope :approved, -> { where(status: 'approved') }
67
- end
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
- posts = Post.all.eager_group(:comments_average_rating, :approved_comments_count)
84
- posts.each do |post|
85
- post.comments_average_rating
86
- post.approved_comments_count
87
- end
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
- post = Post.first
97
- post.commets_average_rating
98
- post.approved_comments_count
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
 
@@ -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', '~> 1.3'
30
+ spec.add_development_dependency 'sqlite3'
31
31
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ActiveRecord::Base
4
- class <<self
4
+ class << self
5
5
  # Post.eager_group(:approved_comments_count, :comments_average_rating)
6
6
  delegate :eager_group, to: :all
7
7
  end
@@ -8,6 +8,7 @@ class ActiveRecord::Relation
8
8
  EagerGroup::Preloader.new(klass, records, eager_group_values).run if eager_group_values.present?
9
9
  records
10
10
  end
11
+
11
12
  alias_method_chain :exec_queries, :eager_group
12
13
 
13
14
  def eager_group(*args)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module EagerGroup
4
4
  class Definition
5
- attr_reader :association, :aggregate_function, :column_name, :scope
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.aggregate_function, definition.column_name)
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.aggregate_function, definition.column_name)
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] || 0)
49
+ record.send("#{definition_key}=", aggregate_hash[id] || definition.default_value)
45
50
  end
46
51
  end
47
52
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerGroup
4
- VERSION = '0.6.1'
4
+ VERSION = '0.7.0'
5
5
  end
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.6.1
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-03-05 00:00:00.000000000 Z
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: '1.3'
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: '1.3'
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.1
155
+ rubygems_version: 3.0.3
156
156
  signing_key:
157
157
  specification_version: 4
158
158
  summary: Fix n+1 aggregate sql functions