activerecord-precount 0.4.2 → 0.4.3

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
  SHA1:
3
- metadata.gz: 558ab186a4ad75d87bd48d8035248b0a72170a33
4
- data.tar.gz: 41746e4e827dfa0bb315730e0827682efef19072
3
+ metadata.gz: 03bcfbd6e4eeb0e73abd3ba62ce3479ce88be195
4
+ data.tar.gz: ed4a627ba81085af77fc6c0fc0c5036d6aed498b
5
5
  SHA512:
6
- metadata.gz: 03436420d1477fe170c6768e9b06f4c4b9ced3bd4ed4c91584023d00a88f789c8d204cab60701b3f3e8f0ee6027626b47ab62ca3c80581414b0e613ff0f61f4d
7
- data.tar.gz: 6c7ce07a028ff757ba67fe6620de75204fa67c5d29f503569839c412890a412996c9e2345938b5fabb763339d27a6f41a7e7ee8eca1e16bfa82c17dfe37e697d
6
+ metadata.gz: 9ec150f0c07963351a74c8657673bb14a3afe9e4476c19658ea74a1e489b89e21f5abc876aa097c703460db396069cacf54bfd7a70ab0e536c9a20c953d5a46f
7
+ data.tar.gz: 0c50e140ed0cfb414f64c380e3fd95c92083a207e2b2dbf144536d285d24bc1c23ded87944eeb74ecce362aa493c818b5fc0097621e9ea32902b7eb73f8433bf
data/README.md CHANGED
@@ -32,7 +32,19 @@ Tweet.all.precount(:favorites).each do |tweet|
32
32
  p tweet.favorites.count
33
33
  end
34
34
  # SELECT `tweets`.* FROM `tweets`
35
- # SELECT `tweets`.`in_reply_to_tweet_id` FROM `tweets` WHERE `tweets`.`tweet_id` IN (1, 2, 3, 4, 5)
35
+ # SELECT COUNT(`tweets`.`tweet_id`), tweet_id FROM `tweets` WHERE `tweets`.`tweet_id` IN (1, 2, 3, 4, 5) GROUP BY tweet_id
36
+ ```
37
+
38
+ ## Benchmark
39
+
40
+ With [this benchmark](https://github.com/k0kubun/activerecord-precount/blob/079c8fdaaca4e7f08f542f825e296183a3f19c67/benchmark.rb)
41
+ ([result](https://travis-ci.org/k0kubun/activerecord-precount/jobs/48996451)),
42
+ precounted query is **7.7x faster** than N+1 count query.
43
+
44
+ ```rb
45
+ # Tweet count is 50, and each tweet has 10 favorites
46
+ Tweet.precount(:favorites).first(50).map(&:favorites_count) # 0.190
47
+ Tweet.first(50).map{ |t| t.favorites.count } # 1.472
36
48
  ```
37
49
 
38
50
  ## Installation
@@ -54,16 +66,48 @@ gem 'activerecord-precount'
54
66
  - mysql
55
67
  - postgresql
56
68
 
57
- ## Testing
69
+ ## Advanced Usage
70
+
71
+ ### Nested eager loading
72
+ `Foo.precount(:bars)` automatically defines `bars_count` association for `Foo`.
73
+ Thus you can preload the association and call `foo.bars_count`.
74
+
75
+ You can manually define `bars_count` with follwoing code.
76
+
77
+ ```diff
78
+ class Foo < ActiveRecord::Base
79
+ - has_many :bars
80
+ + has_many :bars, count_loader: true
81
+ end
82
+ ```
83
+
84
+ Then there are two different ways to preload the `bars_count`.
85
+
86
+ ```rb
87
+ # the same
88
+ Foo.preload(:bars_count)
89
+ Foo.precount(:bars)
90
+ ```
91
+
92
+ With this condition, you can eagerly load nested association by preload.
93
+
94
+ ```rb
95
+ Hoge.preload(foo: :bars_count)
96
+ ```
97
+
98
+ ### Performance issue
99
+
100
+ With activerecord-precount gem installed, `bars.count` fallbacks to `bars_count` if `bars_count` is defined.
101
+ Though precounted `bars.count` is faster than not-precounted one, the fallback is currently much slower than just calling `bars_count`.
102
+
103
+ ```rb
104
+ # slow
105
+ Foo.precount(:bars).map { |f| f.bars.count }
58
106
 
59
- ```bash
60
- $ bundle exec rake
107
+ # fast (recommended)
108
+ Foo.precount(:bars).map { |f| f.bars_count }
61
109
  ```
62
110
 
63
- ## Contributing
111
+ ## License
64
112
 
65
- 1. Fork it ( https://github.com/k0kubun/activerecord-precount/fork )
66
- 2. Create your feature branch (`git checkout -b my-new-feature`)
67
- 3. Commit your changes (`git commit -am 'Add some feature'`)
68
- 4. Push to the branch (`git push origin my-new-feature`)
69
- 5. Create a new Pull Request
113
+ MIT License
data/benchmark.rb CHANGED
@@ -8,8 +8,9 @@ require 'models/tweet'
8
8
  RBench.run(50) do
9
9
  column :counter_cache, title: 'counter_cache'
10
10
  column :left_join, title: 'LEFT JOIN'
11
- column :count_loader, title: 'precount has_many'
12
- column :has_many, title: 'preload has_many'
11
+ column :count_loader, title: 'precount'
12
+ column :precount, title: 'slow precount'
13
+ column :has_many, title: 'preload'
13
14
  column :count_query, title: 'N+1 COUNT'
14
15
 
15
16
  join_relation = Tweet.joins('LEFT JOIN favorites ON tweets.id = favorites.tweet_id').
@@ -26,6 +27,7 @@ RBench.run(50) do
26
27
  [10, 5],
27
28
  [20, 20],
28
29
  [30, 100],
30
+ [50, 10],
29
31
  ]
30
32
 
31
33
  test_cases.each do |tweets_count, favorites_count|
@@ -34,7 +36,8 @@ RBench.run(50) do
34
36
  report "N = #{tweets_count}, count = #{favorites_count}" do
35
37
  counter_cache { Tweet.first(tweets_count).map(&:favorites_count_cache) }
36
38
  left_join { join_relation.first(tweets_count).map(&:joined_count) }
37
- count_loader { Tweet.preload(:favorites_count).first(tweets_count).map(&:favorites_count) }
39
+ count_loader { Tweet.precount(:favorites).first(tweets_count).map(&:favorites_count) }
40
+ precount { Tweet.precount(:favorites).first(tweets_count).map { |t| t.favorites.count } }
38
41
  has_many { Tweet.preload(:favorites).first(tweets_count).map{ |t| t.favorites.size } }
39
42
  count_query { Tweet.first(tweets_count).map{ |t| t.favorites.count } }
40
43
  end
@@ -14,10 +14,7 @@ module ActiveRecord
14
14
 
15
15
  def preload(preloader)
16
16
  associated_records_by_owner(preloader).each do |owner, associated_records|
17
- count = associated_records.count
18
-
19
- association = owner.association(reflection.name)
20
- association.target = count
17
+ owner.association(reflection.name).target = associated_records.first.to_i
21
18
  end
22
19
  end
23
20
 
@@ -26,14 +23,13 @@ module ActiveRecord
26
23
  records_for(slice)
27
24
  }
28
25
 
29
- @preloaded_records.map { |record|
30
- key = record
31
- [record, key]
26
+ @preloaded_records.first.map { |key, count|
27
+ [count, key]
32
28
  }
33
29
  end
34
30
 
35
31
  def query_scope(ids)
36
- scope.where(association_key.in(ids)).pluck(association_key_name)
32
+ scope.where(association_key.in(ids)).group(association_key_name).count(association_key_name)
37
33
  end
38
34
  end
39
35
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Precount
3
- VERSION = "0.4.2"
3
+ VERSION = "0.4.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-precount
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun