eager_group 0.5.0 → 0.7.2

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