activerecord-precount 0.4.2 → 0.4.3

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