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 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