eager_group 0.5.0 → 0.7.2

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
- SHA1:
3
- metadata.gz: 3ddfe7747a2b77e9f26cbbbb0029b41acb03b88b
4
- data.tar.gz: 5357c3d44af2542594fc5ea593029cd3e3c5cc7e
2
+ SHA256:
3
+ metadata.gz: 27ca5cd0d0757f48f272d4349a6dcad1f54992946d960f364bec0086a7646ccf
4
+ data.tar.gz: 27d7def8639caca795400ba7912789ba7098315578ec954dfb2610fe5a6262af
5
5
  SHA512:
6
- metadata.gz: 80419fb36e918d83f69487633330ffd607d806abaed2ff0a10ecd28134402968105c336aa31e90d361511dc717f3d3bfa3deb7a8a2892663e2a7efc7348ad862
7
- data.tar.gz: ca82b49a3007370aebc5b3f2db9d85d51b3aeda6ee800a16dc20a3bda5a8b136c96bfbcfc7ffeb6523a110ef4083c20859ed0d31abb3cf6df4fe2b450d3d5c04
6
+ metadata.gz: 464cafc22781b01be11027b832219b1dc551f0b3c300f3b377296690c0b9e7d4e98dde5f87c7e9ad8f4f88792ed4b908e00347ab95042a66412a3ef1d31731b6
7
+ data.tar.gz: 81aaa99f6d856239dba7e1a06fe4d8e2fb2781e5cb98557d142eac440a658cb4ea2a90c354ea82b33b91dcd2f2fa9452a64cf6f2e0c749282937d682fee62618
@@ -1,5 +1,26 @@
1
1
  # Next Release
2
2
 
3
+ ## 0.7.2 (10/10/2019)
4
+
5
+ * Simplify `association_klass` for `first_object` and `last_object`
6
+
7
+ ## 0.7.1 (08/23/2019)
8
+
9
+ * Set `eager_group_definitions` by `mattr_accessor`
10
+
11
+ ## 0.7.0 (08/22/2019)
12
+
13
+ * Add `first_object` and `last_object` aggregation
14
+
15
+ ## 0.6.1 (03/05/2018)
16
+
17
+ * Skip preload when association is empty
18
+
19
+ ## 0.6.0 (12/15/2018)
20
+
21
+ * Support hash as `eager_group` argument
22
+ * Support rails 5.x
23
+
3
24
  ## 0.5.0 (09/22/2016)
4
25
 
5
26
  * Add magic method for one record
data/README.md CHANGED
@@ -1,34 +1,44 @@
1
1
  # EagerGroup
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/xinminlabs/eager_group.png)](http://travis-ci.org/xinminlabs/eager_group)
3
+ [![Build Status](https://secure.travis-ci.org/flyerhzm/eager_group.png)](http://travis-ci.org/flyerhzm/eager_group)
4
+ [![AwesomeCode Status for
5
+ flyerhzm/eager_group](https://awesomecode.io/projects/e5386790-9420-4003-831a-c9a8c8a48108/status)](https://awesomecode.io/repos/flyerhzm/eager_group)
4
6
 
5
- [More explaination on our blog](http://blog.xinminlabs.com/2015/06/29/eager_group/)
7
+ [More explaination on our blog](http://blog.flyerhzm.com/2015/06/29/eager_group/)
6
8
 
7
9
  Fix n+1 aggregate sql functions for rails, like
8
10
 
9
- SELECT "posts".* FROM "posts";
10
- SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."status" = 'approved'
11
- SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = 2 AND "comments"."status" = 'approved'
12
- 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
+ ```
13
17
 
14
18
  =>
15
19
 
16
- SELECT "posts".* FROM "posts";
17
- 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
+ ```
18
24
 
19
25
  or
20
26
 
21
- SELECT "posts".* FROM "posts";
22
- SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 1;
23
- SELECT AVG("comments"."rating") AS avg_id FROM "comments" WHERE "comments"."post_id" = 2;
24
- 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
+ ```
25
33
 
26
34
  =>
27
35
 
28
- SELECT "posts".* FROM "posts";
29
- 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
+ ```
30
40
 
31
- It only supports Rails 4.x so far.
41
+ It supports Rails 4.x, Rails 5.x and Rails 6.x
32
42
 
33
43
  ## Installation
34
44
 
@@ -40,29 +50,35 @@ gem 'eager_group'
40
50
 
41
51
  And then execute:
42
52
 
43
- $ bundle
53
+ ```
54
+ $ bundle
55
+ ```
44
56
 
45
57
  Or install it yourself as:
46
58
 
47
- $ gem install eager_group
59
+ ```
60
+ $ gem install eager_group
61
+ ```
48
62
 
49
63
  ## Usage
50
64
 
51
65
  First you need to define what aggregate function you want to eager
52
66
  load.
53
67
 
54
- class Post < ActiveRecord::Base
55
- has_many :comments
68
+ ```ruby
69
+ class Post < ActiveRecord::Base
70
+ has_many :comments
56
71
 
57
- define_eager_group :comments_average_rating, :comments, :average, :rating
58
- define_eager_group :approved_comments_count, :comments, :count, :*, -> { approved }
59
- end
72
+ define_eager_group :comments_average_rating, :comments, :average, :rating
73
+ define_eager_group :approved_comments_count, :comments, :count, :*, -> { approved }
74
+ end
60
75
 
61
- class Comment < ActiveRecord::Base
62
- belongs_to :post
76
+ class Comment < ActiveRecord::Base
77
+ belongs_to :post
63
78
 
64
- scope :approved, -> { where(status: 'approved') }
65
- end
79
+ scope :approved, -> { where(status: 'approved') }
80
+ end
81
+ ```
66
82
 
67
83
  The parameters for `define_eager_group` are as follows
68
84
 
@@ -71,29 +87,75 @@ method, it also generates a method with the same name to fetch the
71
87
  result.
72
88
  * `association`, association name you want to aggregate.
73
89
  * `aggregate_function`, aggregate sql function, can be one of `average`,
74
- `count`, `maximum`, `minimum`, `sum`.
90
+ `count`, `maximum`, `minimum`, `sum`, I define 2 additional aggregate
91
+ function `first_object` and `last_object` to eager load first and last
92
+ association objects.
75
93
  * `column_name`, aggregate column name, it can be `:*` for `count`
76
94
  * `scope`, scope is optional, it's used to filter data for aggregation.
77
95
 
78
96
  Then you can use `eager_group` to fix n+1 aggregate sql functions
79
97
  when querying
80
98
 
81
- posts = Post.all.eager_group(:comments_average_rating, :approved_comments_count)
82
- posts.each do |post|
83
- post.comments_average_rating
84
- post.approved_comments_count
85
- 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
+ ```
86
106
 
87
107
  EagerGroup will execute `GROUP BY` sqls for you then set the value of
88
108
  attributes.
89
109
 
90
- `define_eager_group` will define a method in model.
91
- You can call the `definition_name` directly for convenience,
110
+ `define_eager_group` will define a method in model.
111
+ You can call the `definition_name` directly for convenience,
92
112
  but it would not help you to fix n+1 aggregate sql issue.
93
113
 
94
- post = Post.first
95
- post.commets_average_rating
96
- post.approved_comments_count
114
+ ```
115
+ post = Post.first
116
+ post.commets_average_rating
117
+ post.approved_comments_count
118
+ ```
119
+
120
+ ## Advanced
121
+
122
+ `eager_group` through association
123
+
124
+ ```ruby
125
+ User.limit(10).includes(:posts).eager_group(posts: [:comments_average_rating, :approved_comments_count])
126
+ ```
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 eager load first and
142
+ last association objects.
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
+
97
159
 
98
160
  ## Benchmark
99
161
 
@@ -103,6 +165,6 @@ times faster, WOW!
103
165
 
104
166
  ## Contributing
105
167
 
106
- Bug reports and pull requests are welcome on GitHub at https://github.com/xinminlabs/eager_group.
168
+ Bug reports and pull requests are welcome on GitHub at https://github.com/flyerhzm/eager_group.
107
169
 
108
- [1]: https://github.com/xinminlabs/eager_group/blob/master/benchmark.rb
170
+ [1]: https://github.com/flyerhzm/eager_group/blob/master/benchmark.rb
data/Rakefile CHANGED
@@ -1 +1 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Calculating -------------------------------------
2
4
  # Without EagerGroup 2.000 i/100ms
3
5
  # With EagerGroup 28.000 i/100ms
@@ -28,22 +30,22 @@ class Comment < ActiveRecord::Base
28
30
  end
29
31
 
30
32
  # create database eager_group_benchmark;
31
- ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'eager_group_benchmark', :server => '/tmp/mysql.socket', :username => 'root')
33
+ ActiveRecord::Base.establish_connection(
34
+ adapter: 'mysql2', database: 'eager_group_benchmark', server: '/tmp/mysql.socket', username: 'root'
35
+ )
32
36
 
33
- ActiveRecord::Base.connection.tables.each do |table|
34
- ActiveRecord::Base.connection.drop_table(table)
35
- end
37
+ ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
36
38
 
37
39
  ActiveRecord::Schema.define do
38
40
  self.verbose = false
39
41
 
40
- create_table :posts, :force => true do |t|
42
+ create_table :posts, force: true do |t|
41
43
  t.string :title
42
44
  t.string :body
43
45
  t.timestamps null: false
44
46
  end
45
47
 
46
- create_table :comments, :force => true do |t|
48
+ create_table :comments, force: true do |t|
47
49
  t.string :body
48
50
  t.string :status
49
51
  t.integer :rating
@@ -53,30 +55,31 @@ ActiveRecord::Schema.define do
53
55
  end
54
56
 
55
57
  posts_size = 100
56
- comments_size = 1000
58
+ comments_size = 1_000
57
59
 
58
60
  posts = []
59
- posts_size.times do |i|
60
- posts << Post.new(:title => "Title #{i}", :body => "Body #{i}")
61
- end
61
+ posts_size.times { |i| posts << Post.new(title: "Title #{i}", body: "Body #{i}") }
62
62
  Post.import posts
63
63
  post_ids = Post.all.pluck(:id)
64
64
 
65
65
  comments = []
66
66
  comments_size.times do |i|
67
- comments << Comment.new(:body => "Comment #{i}", :post_id => post_ids[i%100], :status => ["approved", "deleted"][i%2], rating: i%5+1)
67
+ comments <<
68
+ Comment.new(
69
+ body: "Comment #{i}", post_id: post_ids[i % 100], status: %w[approved deleted][i % 2], rating: i % 5 + 1
70
+ )
68
71
  end
69
72
  Comment.import comments
70
73
 
71
74
  Benchmark.ips do |x|
72
- x.report("Without EagerGroup") do
75
+ x.report('Without EagerGroup') do
73
76
  Post.limit(20).each do |post|
74
77
  post.comments.approved.count
75
78
  post.comments.approved.average('rating')
76
79
  end
77
80
  end
78
81
 
79
- x.report("With EagerGroup") do
82
+ x.report('With EagerGroup') do
80
83
  Post.eager_group(:approved_comments_count, :comments_average_rating).limit(20).each do |post|
81
84
  post.approved_comments_count
82
85
  post.comments_average_rating
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "eager_group"
4
+ require 'bundler/setup'
5
+ require 'eager_group'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "eager_group"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start
@@ -1,30 +1,32 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'eager_group/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "eager_group"
8
+ spec.name = 'eager_group'
8
9
  spec.version = EagerGroup::VERSION
9
- spec.authors = ["Richard Huang"]
10
- spec.email = ["flyerhzm@gmail.com"]
10
+ spec.authors = ['Richard Huang']
11
+ spec.email = ['flyerhzm@gmail.com']
11
12
 
12
- spec.summary = %q{Fix n+1 aggregate sql functions}
13
- spec.description = %q{Fix n+1 aggregate sql functions for rails}
14
- spec.homepage = "https://github.com/xinminlabs/eager_group"
13
+ spec.summary = 'Fix n+1 aggregate sql functions'
14
+ spec.description = 'Fix n+1 aggregate sql functions for rails'
15
+ spec.homepage = 'https://github.com/flyerhzm/eager_group'
15
16
 
16
- spec.license = 'MIT'
17
+ spec.license = 'MIT'
17
18
 
18
19
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
- spec.bindir = "exe"
20
+ spec.bindir = 'exe'
20
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
- spec.require_paths = ["lib"]
22
+ spec.require_paths = ['lib']
22
23
 
23
- spec.add_development_dependency "bundler"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "rspec", "~> 3.3"
26
- spec.add_development_dependency "sqlite3", "~> 1.3"
27
- spec.add_development_dependency "activerecord"
28
- spec.add_development_dependency "activerecord-import"
29
- spec.add_development_dependency "benchmark-ips"
24
+ spec.add_development_dependency 'activerecord'
25
+ spec.add_development_dependency 'activerecord-import'
26
+ spec.add_development_dependency 'activesupport'
27
+ spec.add_development_dependency 'benchmark-ips'
28
+ spec.add_development_dependency 'bundler'
29
+ spec.add_development_dependency 'rake', '~> 10.0'
30
+ spec.add_development_dependency 'rspec', '~> 3.3'
31
+ spec.add_development_dependency 'sqlite3'
30
32
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module WithEagerGroup
5
+ def exec_queries
6
+ records = super
7
+ EagerGroup::Preloader.new(klass, records, eager_group_values).run if eager_group_values.present?
8
+ records
9
+ end
10
+
11
+ def eager_group(*args)
12
+ check_if_method_has_arguments!('eager_group', args)
13
+ spawn.eager_group!(*args)
14
+ end
15
+
16
+ def eager_group!(*args)
17
+ self.eager_group_values += args
18
+ self
19
+ end
20
+
21
+ def eager_group_values
22
+ @values[:eager_group] || []
23
+ end
24
+
25
+ def eager_group_values=(values)
26
+ raise ImmutableRelation if @loaded
27
+
28
+ @values[:eager_group] = values
29
+ end
30
+ end
31
+ end
@@ -1,7 +1,7 @@
1
- require "eager_group/version"
2
- require 'active_record'
3
- require 'eager_group/active_record_base'
4
- require 'eager_group/active_record_relation'
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/attribute_accessors'
4
+ require 'eager_group/version'
5
5
 
6
6
  module EagerGroup
7
7
  autoload :Preloader, 'eager_group/preloader'
@@ -12,36 +12,44 @@ module EagerGroup
12
12
  end
13
13
 
14
14
  module ClassMethods
15
- attr_reader :eager_group_definations
15
+ mattr_accessor :eager_group_definitions, default: {}
16
16
 
17
17
  # class Post
18
18
  # define_eager_group :comments_avergage_rating, :comments, :average, :rating
19
19
  # define_eager_group :approved_comments_count, :comments, :count, :*, -> { approved }
20
20
  # end
21
21
  def define_eager_group(attr, association, aggregate_function, column_name, scope = nil)
22
- self.send :attr_accessor, attr
23
- @eager_group_definations ||= {}
24
- @eager_group_definations[attr] = Definition.new association, aggregate_function, column_name, scope
25
-
26
- define_method attr, -> (*args) do
27
- query_result_cache = instance_variable_get("@#{attr}")
28
- if args.blank? && query_result_cache.present?
29
- return query_result_cache
30
- end
31
- preload_eager_group(attr, *args)
32
- instance_variable_get("@#{attr}")
33
- end
34
-
22
+ send :attr_accessor, attr
23
+ eager_group_definitions[attr] = Definition.new(association, aggregate_function, column_name, scope)
24
+
25
+ define_method attr,
26
+ lambda { |*args|
27
+ query_result_cache = instance_variable_get("@#{attr}")
28
+ return query_result_cache if args.blank? && query_result_cache.present?
29
+
30
+ preload_eager_group(attr, *args)
31
+ instance_variable_get("@#{attr}")
32
+ }
33
+
35
34
  define_method "#{attr}=" do |val|
36
35
  instance_variable_set("@#{attr}", val)
37
36
  end
38
37
  end
39
38
  end
40
-
39
+
41
40
  private
41
+
42
42
  def preload_eager_group(*eager_group_value)
43
43
  EagerGroup::Preloader.new(self.class, [self], [eager_group_value]).run
44
44
  end
45
45
  end
46
46
 
47
- ActiveRecord::Base.send :include, EagerGroup
47
+ require 'active_record'
48
+ ActiveRecord::Base.class_eval do
49
+ include EagerGroup
50
+ class << self
51
+ delegate :eager_group, to: :all
52
+ end
53
+ end
54
+ require 'active_record/with_eager_group'
55
+ ActiveRecord::Relation.send :prepend, ActiveRecord::WithEagerGroup
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ActiveRecord::Base
2
- class <<self
4
+ class << self
3
5
  # Post.eager_group(:approved_comments_count, :comments_average_rating)
4
- delegate :eager_group, :to => :all
6
+ delegate :eager_group, to: :all
5
7
  end
6
8
  end
@@ -1,13 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ActiveRecord::Relation
2
4
  # Post.all.eager_group(:approved_comments_count, :comments_average_rating)
3
5
 
4
6
  def exec_queries_with_eager_group
5
7
  records = exec_queries_without_eager_group
6
- if eager_group_values.present?
7
- EagerGroup::Preloader.new(self.klass, records, eager_group_values).run
8
- end
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)
@@ -26,6 +27,7 @@ class ActiveRecord::Relation
26
27
 
27
28
  def eager_group_values=(values)
28
29
  raise ImmutableRelation if @loaded
30
+
29
31
  @values[:eager_group] = values
30
32
  end
31
33
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EagerGroup
2
4
  class Definition
3
- attr_reader :association, :aggregate_function, :column_name, :scope
5
+ attr_reader :association, :column_name, :scope
4
6
 
5
7
  def initialize(association, aggregate_function, column_name, scope)
6
8
  @association = association
@@ -8,5 +10,20 @@ module EagerGroup
8
10
  @column_name = column_name
9
11
  @scope = scope
10
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
+
18
+ @aggregate_function
19
+ end
20
+
21
+ def need_load_object
22
+ %i[first_object last_object].include?(@aggregate_function.to_sym)
23
+ end
24
+
25
+ def default_value
26
+ %i[first_object last_object].include?(@aggregate_function.to_sym) ? nil : 0
27
+ end
11
28
  end
12
29
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EagerGroup
2
4
  class Preloader
3
5
  def initialize(klass, records, eager_group_values)
@@ -9,40 +11,52 @@ module EagerGroup
9
11
  # Preload aggregate functions
10
12
  def run
11
13
  primary_key = @klass.primary_key
12
- record_ids = @records.map { |record| record.send primary_key }
13
14
  @eager_group_values.each do |eager_group_value|
14
- defination_key, arguments = eager_group_value.is_a?(Array) ? [eager_group_value.shift, eager_group_value] : [eager_group_value, nil]
15
- if definition = @klass.eager_group_definations[defination_key]
16
- reflection = @klass.reflect_on_association(definition.association)
17
- association_class = reflection.klass
18
- association_class = association_class.instance_exec(*arguments, &definition.scope) if definition.scope
19
- polymophic_as_condition = lambda {|reflection|
20
- if reflection.type
21
- ["#{reflection.name}.#{reflection.type} = ?", @klass.base_class.name]
22
- else
23
- []
24
- end
25
- }
15
+ definition_key, arguments =
16
+ eager_group_value.is_a?(Array) ? [eager_group_value.shift, eager_group_value] : [eager_group_value, nil]
17
+ if definition_key.is_a?(Hash)
18
+ association_name, definition_key = *definition_key.first
19
+ @records = @records.flat_map { |record| record.send(association_name) }
20
+ next if @records.empty?
21
+
22
+ @klass = @records.first.class
23
+ end
24
+ record_ids = @records.map { |record| record.send(primary_key) }
25
+ next unless definition = @klass.eager_group_definitions[definition_key]
26
26
 
27
- if reflection.through_reflection
28
- foreign_key = "#{reflection.through_reflection.name}.#{reflection.through_reflection.foreign_key}"
29
- aggregate_hash = association_class.joins(reflection.through_reflection.name)
30
- .where("#{foreign_key} IN (?)", record_ids)
31
- .where(polymophic_as_condition.call(reflection.through_reflection))
32
- .group("#{foreign_key}")
33
- .send(definition.aggregate_function, definition.column_name)
34
- else
35
- aggregate_hash = association_class.where(reflection.foreign_key => record_ids)
36
- .where(polymophic_as_condition.call(reflection))
37
- .group(reflection.foreign_key)
38
- .send(definition.aggregate_function, definition.column_name)
39
- end
40
- @records.each do |record|
41
- id = record.send primary_key
42
- record.send "#{defination_key}=", aggregate_hash[id] || 0
43
- end
27
+ reflection = @klass.reflect_on_association(definition.association)
28
+ association_class = reflection.klass
29
+ association_class = association_class.instance_exec(*arguments, &definition.scope) if definition.scope
30
+
31
+ if reflection.through_reflection
32
+ foreign_key = "#{reflection.through_reflection.name}.#{reflection.through_reflection.foreign_key}"
33
+ aggregate_hash =
34
+ association_class.joins(reflection.through_reflection.name).where(foreign_key => record_ids).where(
35
+ polymophic_as_condition(reflection.through_reflection)
36
+ )
37
+ .group(foreign_key)
38
+ .send(definition.aggregation_function, definition.column_name)
39
+ else
40
+ aggregate_hash =
41
+ association_class.where(reflection.foreign_key => record_ids).where(polymophic_as_condition(reflection))
42
+ .group(reflection.foreign_key)
43
+ .send(definition.aggregation_function, definition.column_name)
44
+ end
45
+ if definition.need_load_object
46
+ aggregate_objects = reflection.klass.find(aggregate_hash.values).each_with_object({}) { |o, h| h[o.id] = o }
47
+ aggregate_hash.keys.each { |key| aggregate_hash[key] = aggregate_objects[aggregate_hash[key]] }
48
+ end
49
+ @records.each do |record|
50
+ id = record.send(primary_key)
51
+ record.send("#{definition_key}=", aggregate_hash[id] || definition.default_value)
44
52
  end
45
53
  end
46
54
  end
55
+
56
+ private
57
+
58
+ def polymophic_as_condition(reflection)
59
+ reflection.type ? { reflection.name => { reflection.type => @klass.base_class.name } } : []
60
+ end
47
61
  end
48
62
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module EagerGroup
2
- VERSION = "0.5.0"
4
+ VERSION = '0.7.2'
3
5
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eager_group
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-09-22 00:00:00.000000000 Z
11
+ date: 2019-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -25,49 +25,49 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: activerecord-import
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec
42
+ name: activesupport
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '3.3'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '3.3'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: sqlite3
56
+ name: benchmark-ips
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '1.3'
61
+ version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '1.3'
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: activerecord
70
+ name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,21 +81,35 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: activerecord-import
84
+ name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '10.0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '10.0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: benchmark-ips
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - ">="
@@ -126,13 +140,14 @@ files:
126
140
  - bin/console
127
141
  - bin/setup
128
142
  - eager_group.gemspec
143
+ - lib/active_record/with_eager_group.rb
129
144
  - lib/eager_group.rb
130
145
  - lib/eager_group/active_record_base.rb
131
146
  - lib/eager_group/active_record_relation.rb
132
147
  - lib/eager_group/definition.rb
133
148
  - lib/eager_group/preloader.rb
134
149
  - lib/eager_group/version.rb
135
- homepage: https://github.com/xinminlabs/eager_group
150
+ homepage: https://github.com/flyerhzm/eager_group
136
151
  licenses:
137
152
  - MIT
138
153
  metadata: {}
@@ -151,8 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
166
  - !ruby/object:Gem::Version
152
167
  version: '0'
153
168
  requirements: []
154
- rubyforge_project:
155
- rubygems_version: 2.5.1
169
+ rubygems_version: 3.0.3
156
170
  signing_key:
157
171
  specification_version: 4
158
172
  summary: Fix n+1 aggregate sql functions