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 +4 -4
- data/README.md +54 -10
- data/benchmark.rb +6 -3
- data/lib/active_record/associations/preloader/count_loader.rb +4 -8
- data/lib/active_record/precount/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03bcfbd6e4eeb0e73abd3ba62ce3479ce88be195
|
4
|
+
data.tar.gz: ed4a627ba81085af77fc6c0fc0c5036d6aed498b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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`.`
|
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
|
-
##
|
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
|
-
|
60
|
-
|
107
|
+
# fast (recommended)
|
108
|
+
Foo.precount(:bars).map { |f| f.bars_count }
|
61
109
|
```
|
62
110
|
|
63
|
-
##
|
111
|
+
## License
|
64
112
|
|
65
|
-
|
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
|
12
|
-
column :
|
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.
|
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
|
-
|
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 { |
|
30
|
-
key
|
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)).
|
32
|
+
scope.where(association_key.in(ids)).group(association_key_name).count(association_key_name)
|
37
33
|
end
|
38
34
|
end
|
39
35
|
end
|