activerecord-precount 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -13
- data/benchmark.rb +17 -8
- data/lib/active_record/precount/base_extension.rb +1 -1
- data/lib/active_record/precount/join_dependency_extension.rb +39 -13
- data/lib/active_record/precount/relation_extension.rb +27 -1
- data/lib/active_record/precount/version.rb +1 -1
- data/sample/app/controllers/application_controller.rb +24 -4
- data/sample/app/views/application/index.html.erb +6 -4
- data/sample/db/migrate/20141122002518_create_tweets.rb +1 -0
- data/sample/db/migrate/20141122002548_create_favorites.rb +1 -0
- data/sample/db/schema.rb +4 -0
- data/test/cases/associations/eager_count_test.rb +46 -0
- data/test/cases/associations/eager_load_test.rb +19 -6
- data/test/cases/associations/precount_test.rb +13 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b808e109c4ff4e66a60696bef99eb38a78f2c729
|
4
|
+
data.tar.gz: abed0c7c35ef630938eec845e4439a7e62f54e90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36e5f8ad70b97aa1d58990791ec8a7de3e075783700d25e8ffc93be693c2d9f39b29da237b46d5159e750654ea3b50b80c99bf32c31118618025e63957aac590
|
7
|
+
data.tar.gz: c08a6d413c210e044e609e36c1e297803476ff52e6b54c6bd8be48b89c4aeafc3b2d759da86314b84858d18fdf29ecb0c9fda2b1f8403bbdce44dfa829bd5aa0
|
data/README.md
CHANGED
@@ -15,36 +15,55 @@ Tweet.all.each do |tweet|
|
|
15
15
|
p tweet.favorites.count
|
16
16
|
end
|
17
17
|
# SELECT `tweets`.* FROM `tweets`
|
18
|
-
# SELECT COUNT(*) FROM `
|
19
|
-
# SELECT COUNT(*) FROM `
|
20
|
-
# SELECT COUNT(*) FROM `
|
21
|
-
# SELECT COUNT(*) FROM `
|
22
|
-
# SELECT COUNT(*) FROM `
|
18
|
+
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 1
|
19
|
+
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 2
|
20
|
+
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 3
|
21
|
+
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 4
|
22
|
+
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 5
|
23
23
|
```
|
24
24
|
|
25
25
|
### Count eager loading
|
26
26
|
|
27
|
+
#### precount
|
28
|
+
|
27
29
|
With activerecord-precount gem installed, you can use `precount` method
|
28
30
|
to eagerly load counts of associated records.
|
31
|
+
Like `preload`, it loads counts by multiple queries
|
29
32
|
|
30
33
|
```rb
|
31
34
|
Tweet.all.precount(:favorites).each do |tweet|
|
32
35
|
p tweet.favorites.count
|
33
36
|
end
|
34
37
|
# SELECT `tweets`.* FROM `tweets`
|
35
|
-
# SELECT COUNT(`
|
38
|
+
# SELECT COUNT(`favorites`.`tweet_id`), `favorites`.`tweet_id` FROM `favorites` WHERE `favorites`.`tweet_id` IN (1, 2, 3, 4, 5) GROUP BY `favorites`.`tweet_id`
|
39
|
+
```
|
40
|
+
|
41
|
+
#### eager\_count
|
42
|
+
|
43
|
+
Like `eager_load`, `eager_count` method allows you to load counts by one JOIN query.
|
44
|
+
|
45
|
+
```rb
|
46
|
+
Tweet.all.eager_count(:favorites).each do |tweet|
|
47
|
+
p tweet.favorites.count
|
48
|
+
end
|
49
|
+
# SELECT `tweets`.`id` AS t0_r0, `tweets`.`tweet_id` AS t0_r1, `tweets`.`user_id` AS t0_r2, `tweets`.`created_at` AS t0_r3, `tweets`.`updated_at` AS t0_r4, COUNT(`favorites`.`id`) AS t1_r0 FROM `tweets` LEFT OUTER JOIN `favorites` ON `favorites`.`tweet_id` = `tweets`.`id` GROUP BY tweets.id
|
36
50
|
```
|
37
51
|
|
38
52
|
## Benchmark
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
54
|
+
The [result](https://travis-ci.org/k0kubun/activerecord-precount/jobs/49061937) of
|
55
|
+
[this benchmark](https://github.com/k0kubun/activerecord-precount/blob/40765d36ff0e0627cd0941b2c0a0f6573290c67e/benchmark.rb).
|
56
|
+
|
57
|
+
| | N+1 query | precount | eager\_count |
|
58
|
+
|:-- |:----------|:---------|:-------------|
|
59
|
+
| Time | 1.401 | 0.176 | 0.119 |
|
60
|
+
| Ratio | 1.0x | **7.9x faster** | **11.7x faster** |
|
43
61
|
|
44
62
|
```rb
|
45
63
|
# Tweet count is 50, and each tweet has 10 favorites
|
46
|
-
Tweet.
|
47
|
-
Tweet.
|
64
|
+
Tweet.all.map{ |t| t.favorites.count } # N+1 query
|
65
|
+
Tweet.precount(:favorites).map(&:favorites_count) # precount
|
66
|
+
Tweet.eager_count(:favorites).map(&:favorites_count) # eager_count
|
48
67
|
```
|
49
68
|
|
50
69
|
## Installation
|
@@ -69,8 +88,8 @@ gem 'activerecord-precount'
|
|
69
88
|
## Advanced Usage
|
70
89
|
|
71
90
|
### Nested eager loading
|
72
|
-
`Foo.precount(:bars)` automatically defines `bars_count` association for `Foo`.
|
73
|
-
|
91
|
+
`Foo.precount(:bars)` or `Foo.eager_count(:bars)` automatically defines `bars_count` association for `Foo`.
|
92
|
+
That enables you to preload the association and call `foo.bars_count`.
|
74
93
|
|
75
94
|
You can manually define `bars_count` with follwoing code.
|
76
95
|
|
@@ -103,9 +122,11 @@ Though precounted `bars.count` is faster than not-precounted one, the fallback i
|
|
103
122
|
```rb
|
104
123
|
# slow
|
105
124
|
Foo.precount(:bars).map { |f| f.bars.count }
|
125
|
+
Foo.eager_count(:bars).map { |f| f.bars.count }
|
106
126
|
|
107
127
|
# fast (recommended)
|
108
128
|
Foo.precount(:bars).map { |f| f.bars_count }
|
129
|
+
Foo.eager_count(:bars).map { |f| f.bars_count }
|
109
130
|
```
|
110
131
|
|
111
132
|
## License
|
data/benchmark.rb
CHANGED
@@ -8,8 +8,10 @@ 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 :
|
12
|
-
column :precount, title: '
|
11
|
+
column :eager_count, title: 'eager_count'
|
12
|
+
column :precount, title: 'precount'
|
13
|
+
column :slow_eager_count, title: 'slow eager_count'
|
14
|
+
column :slow_precount, title: 'slow precount'
|
13
15
|
column :has_many, title: 'preload'
|
14
16
|
column :count_query, title: 'N+1 COUNT'
|
15
17
|
|
@@ -17,6 +19,9 @@ RBench.run(50) do
|
|
17
19
|
select('tweets.*, COUNT(favorites.id) AS joined_count').group('tweets.id')
|
18
20
|
|
19
21
|
def prepare_records(tweets_count, favorites_count)
|
22
|
+
Tweet.delete_all
|
23
|
+
Favorite.delete_all
|
24
|
+
|
20
25
|
tweets_count.times do
|
21
26
|
t = Tweet.create(favorites_count_cache: 0)
|
22
27
|
favorites_count.times { Favorite.create(tweet: t) }
|
@@ -34,12 +39,16 @@ RBench.run(50) do
|
|
34
39
|
prepare_records(tweets_count, favorites_count)
|
35
40
|
|
36
41
|
report "N = #{tweets_count}, count = #{favorites_count}" do
|
37
|
-
counter_cache { Tweet.
|
38
|
-
left_join {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
counter_cache { Tweet.all.map(&:favorites_count_cache) }
|
43
|
+
left_join { Tweet.joins('LEFT JOIN favorites ON tweets.id = favorites.tweet_id').
|
44
|
+
select('tweets.*, COUNT(favorites.id) AS joined_count').
|
45
|
+
group('tweets.id').map(&:joined_count) }
|
46
|
+
eager_count { Tweet.eager_count(:favorites).map(&:favorites_count) }
|
47
|
+
precount { Tweet.precount(:favorites).map(&:favorites_count) }
|
48
|
+
slow_eager_count { Tweet.eager_count(:favorites).map { |t| t.favorites.count } }
|
49
|
+
slow_precount { Tweet.precount(:favorites).map { |t| t.favorites.count } }
|
50
|
+
has_many { Tweet.preload(:favorites).map{ |t| t.favorites.size } }
|
51
|
+
count_query { Tweet.all.map{ |t| t.favorites.count } }
|
43
52
|
end
|
44
53
|
end
|
45
54
|
end
|
@@ -1,22 +1,48 @@
|
|
1
1
|
module ActiveRecord
|
2
|
-
# This imitates EagerLoadPolymorphicError
|
3
|
-
class EagerLoadCountLoaderError < ActiveRecordError
|
4
|
-
def initialize(reflection)
|
5
|
-
super("Cannot eagerly load the count_loader association #{reflection.name.inspect}")
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
2
|
module Precount
|
10
3
|
module JoinDependencyExtension
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
4
|
+
class CountTable < Associations::JoinDependency::Aliases::Table
|
5
|
+
def column_aliases
|
6
|
+
columns.map { |column| table[column.name].count.as Arel.sql column.alias }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def aliases
|
11
|
+
Associations::JoinDependency::Aliases.new join_root.each_with_index.map { |join_part,i|
|
12
|
+
if join_part.is_a?(Associations::JoinDependency::JoinAssociation) && join_part.reflection.macro == :count_loader
|
13
|
+
# select COUNT(primary_key)
|
14
|
+
column_name = join_part.reflection.klass.primary_key
|
15
|
+
column = Associations::JoinDependency::Aliases::Column.new column_name, "t#{i}_r0"
|
16
|
+
CountTable.new(join_part, [column])
|
17
|
+
else
|
18
|
+
# original aliases' internal function
|
19
|
+
columns = join_part.column_names.each_with_index.map { |column_name,j|
|
20
|
+
Associations::JoinDependency::Aliases::Column.new column_name, "t#{i}_r#{j}"
|
21
|
+
}
|
22
|
+
Associations::JoinDependency::Aliases::Table.new(join_part, columns)
|
16
23
|
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# instantiate only count_loader and pass others to super
|
30
|
+
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
|
31
|
+
normal_children = []
|
32
|
+
|
33
|
+
parent.children.each do |node|
|
34
|
+
if node.reflection.macro != :count_loader
|
35
|
+
normal_children << node
|
36
|
+
next
|
37
|
+
end
|
38
|
+
|
39
|
+
key = aliases.column_alias(node, node.primary_key)
|
40
|
+
ar_parent.association(node.reflection.name).target = row[key].to_i
|
17
41
|
end
|
42
|
+
return if normal_children.blank?
|
18
43
|
|
19
|
-
|
44
|
+
normal_parent = Associations::JoinDependency::JoinBase.new(parent.base_klass, normal_children)
|
45
|
+
super(ar_parent, normal_parent, row, rs, seen, model_cache, aliases)
|
20
46
|
end
|
21
47
|
end
|
22
48
|
end
|
@@ -13,11 +13,23 @@ module ActiveRecord
|
|
13
13
|
self
|
14
14
|
end
|
15
15
|
|
16
|
+
def eager_count(*args)
|
17
|
+
check_if_method_has_arguments!(:eager_count, args)
|
18
|
+
spawn.eager_count!(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def eager_count!(*args)
|
22
|
+
define_count_loader!(*args)
|
23
|
+
|
24
|
+
self.eager_load_values += args.map { |arg| :"#{arg}_count" }
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
16
28
|
private
|
17
29
|
|
18
30
|
def define_count_loader!(*args)
|
19
31
|
args.each do |arg|
|
20
|
-
raise ArgumentError, "#{
|
32
|
+
raise ArgumentError, "Association named '#{arg}' was not found on #{klass.name}." unless has_reflection?(arg)
|
21
33
|
next if has_reflection?(counter_name = :"#{arg}_count")
|
22
34
|
|
23
35
|
options = reflection_for(arg).options.slice(*Associations::Builder::CountLoader.valid_options)
|
@@ -25,6 +37,20 @@ module ActiveRecord
|
|
25
37
|
Reflection.add_reflection(model, counter_name, reflection)
|
26
38
|
end
|
27
39
|
end
|
40
|
+
|
41
|
+
def apply_join_dependency(relation, join_dependency)
|
42
|
+
relation = super(relation, join_dependency)
|
43
|
+
|
44
|
+
# to count associated records in JOIN query, group scope is necessary
|
45
|
+
join_dependency.reflections.each do |reflection|
|
46
|
+
if reflection.macro == :count_loader
|
47
|
+
ar = reflection.active_record
|
48
|
+
relation = relation.group("#{ar.table_name}.#{ar.primary_key}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
relation
|
53
|
+
end
|
28
54
|
end
|
29
55
|
end
|
30
56
|
end
|
@@ -1,13 +1,33 @@
|
|
1
1
|
class ApplicationController < ActionController::Base
|
2
|
+
AVAILABLE_LOADERS = %w[N+1 precount eager_count].freeze
|
3
|
+
DEFAULT_LOADER = 'N+1'
|
4
|
+
|
2
5
|
def index
|
3
6
|
@tweets = Tweet.all
|
4
|
-
|
7
|
+
case loader
|
8
|
+
when 'precount'
|
5
9
|
@tweets = @tweets.precount(:replies).preload(in_reply_to: :favorites_count)
|
10
|
+
when 'eager_count'
|
11
|
+
@tweets = @tweets.eager_count(:replies).eager_load(in_reply_to: :favorites_count)
|
6
12
|
end
|
7
13
|
end
|
8
14
|
|
9
|
-
def
|
10
|
-
|
15
|
+
def precount?
|
16
|
+
loader == 'precount'
|
17
|
+
end
|
18
|
+
helper_method :precount?
|
19
|
+
|
20
|
+
def eager_count?
|
21
|
+
loader == 'eager_count'
|
22
|
+
end
|
23
|
+
helper_method :eager_count?
|
24
|
+
|
25
|
+
def loader
|
26
|
+
if AVAILABLE_LOADERS.include?(params[:loader])
|
27
|
+
params[:loader]
|
28
|
+
else
|
29
|
+
DEFAULT_LOADER
|
30
|
+
end
|
11
31
|
end
|
12
|
-
helper_method :
|
32
|
+
helper_method :loader
|
13
33
|
end
|
@@ -23,7 +23,7 @@
|
|
23
23
|
<%= tweet.id %>
|
24
24
|
</td>
|
25
25
|
<td>
|
26
|
-
<%= tweet.replies.count %>
|
26
|
+
<%= (precount? || eager_count?) ? tweet.replies_count : tweet.replies.count %>
|
27
27
|
</td>
|
28
28
|
<td>
|
29
29
|
<%= tweet.in_reply_to.try(:id) %>
|
@@ -38,12 +38,14 @@
|
|
38
38
|
<% finish = Time.now %>
|
39
39
|
|
40
40
|
<p>
|
41
|
-
|
41
|
+
Loader: <%= loader %>
|
42
42
|
</p>
|
43
43
|
<p>
|
44
44
|
Time: <%= "%.1f" % ((finish - start) * 1000) %>ms
|
45
45
|
</p>
|
46
46
|
|
47
47
|
<p>
|
48
|
-
|
49
|
-
|
48
|
+
<% ApplicationController::AVAILABLE_LOADERS.each do |ld| %>
|
49
|
+
<%= link_to_unless loader == ld, ld, url_for(loader: ld) %>
|
50
|
+
<% end %>
|
51
|
+
</p>
|
data/sample/db/schema.rb
CHANGED
@@ -20,6 +20,8 @@ ActiveRecord::Schema.define(version: 20141122002555) do
|
|
20
20
|
t.datetime "updated_at"
|
21
21
|
end
|
22
22
|
|
23
|
+
add_index "favorites", ["tweet_id"], name: "index_favorites_on_tweet_id", using: :btree
|
24
|
+
|
23
25
|
create_table "tweets", force: :cascade do |t|
|
24
26
|
t.integer "in_reply_to_tweet_id", limit: 4
|
25
27
|
t.integer "user_id", limit: 4
|
@@ -27,6 +29,8 @@ ActiveRecord::Schema.define(version: 20141122002555) do
|
|
27
29
|
t.datetime "updated_at"
|
28
30
|
end
|
29
31
|
|
32
|
+
add_index "tweets", ["in_reply_to_tweet_id"], name: "index_tweets_on_in_reply_to_tweet_id", using: :btree
|
33
|
+
|
30
34
|
create_table "users", force: :cascade do |t|
|
31
35
|
t.datetime "created_at"
|
32
36
|
t.datetime "updated_at"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'cases/helper'
|
2
|
+
|
3
|
+
class EagerCountTest < ActiveRecord::CountLoader::TestCase
|
4
|
+
def setup
|
5
|
+
tweets_count.times.map do |index|
|
6
|
+
tweet = Tweet.create
|
7
|
+
index.times { Favorite.create(tweet: tweet) }
|
8
|
+
end
|
9
|
+
|
10
|
+
if Tweet.has_reflection?(:favs_count)
|
11
|
+
if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 2
|
12
|
+
Tweet._reflections.delete('favs_count')
|
13
|
+
else
|
14
|
+
Tweet._reflections.delete(:favs_count)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def teardown
|
20
|
+
[Tweet, Favorite].each(&:delete_all)
|
21
|
+
end
|
22
|
+
|
23
|
+
def tweets_count
|
24
|
+
3
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_eager_count_defines_count_loader
|
28
|
+
assert_equal(Tweet.has_reflection?(:favs_count), false)
|
29
|
+
Tweet.eager_count(:favs).map(&:favs_count)
|
30
|
+
assert_equal(Tweet.has_reflection?(:favs_count), true)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_eager_count_has_many_with_count_loader_does_not_execute_n_1_queries
|
34
|
+
assert_queries(1 + tweets_count) { Tweet.all.map { |t| t.favorites.count } }
|
35
|
+
assert_queries(1 + tweets_count) { Tweet.all.map(&:favorites_count) }
|
36
|
+
assert_queries(1) { Tweet.eager_count(:favorites).map { |t| t.favorites.count } }
|
37
|
+
assert_queries(1) { Tweet.eager_count(:favorites).map(&:favorites_count) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_eager_count_has_many_counts_properly
|
41
|
+
expected = Tweet.order(id: :asc).map { |t| t.favorites.count }
|
42
|
+
assert_equal(Tweet.order(id: :asc).map(&:favorites_count), expected)
|
43
|
+
assert_equal(Tweet.order(id: :asc).eager_count(:favorites).map { |t| t.favorites.count }, expected)
|
44
|
+
assert_equal(Tweet.order(id: :asc).eager_count(:favorites).map(&:favorites_count), expected)
|
45
|
+
end
|
46
|
+
end
|
@@ -4,17 +4,30 @@ require 'models/tweet'
|
|
4
4
|
|
5
5
|
class EagerLoadTest < ActiveRecord::CountLoader::TestCase
|
6
6
|
def setup
|
7
|
-
|
8
|
-
|
7
|
+
tweets_count.times.map do |index|
|
8
|
+
tweet = Tweet.create
|
9
|
+
index.times { Favorite.create(tweet: tweet) }
|
10
|
+
end
|
9
11
|
end
|
10
12
|
|
11
13
|
def teardown
|
12
14
|
[Tweet, Favorite].each(&:delete_all)
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
def tweets_count
|
18
|
+
3
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_eager_load_does_not_execute_n_1_queries
|
22
|
+
assert_queries(1 + tweets_count) { Tweet.all.map { |t| t.favorites.count } }
|
23
|
+
assert_queries(1 + tweets_count) { Tweet.all.map(&:favorites_count) }
|
24
|
+
assert_queries(1) { Tweet.eager_load(:favorites_count).map(&:favorites_count) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_eager_loaded_count_loader_counts_properly
|
28
|
+
expected = Tweet.order(id: :asc).map { |t| t.favorites.count }
|
29
|
+
assert_equal(Tweet.order(id: :asc).map(&:favorites_count), expected)
|
30
|
+
assert_equal(Tweet.order(id: :asc).eager_load(:favorites_count).map { |t| t.favorites.count }, expected)
|
31
|
+
assert_equal(Tweet.order(id: :asc).eager_load(:favorites_count).map(&:favorites_count), expected)
|
19
32
|
end
|
20
33
|
end
|
@@ -6,6 +6,14 @@ class PrecountTest < ActiveRecord::CountLoader::TestCase
|
|
6
6
|
tweet = Tweet.create
|
7
7
|
index.times { Favorite.create(tweet: tweet) }
|
8
8
|
end
|
9
|
+
|
10
|
+
if Tweet.has_reflection?(:favs_count)
|
11
|
+
if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 2
|
12
|
+
Tweet._reflections.delete('favs_count')
|
13
|
+
else
|
14
|
+
Tweet._reflections.delete(:favs_count)
|
15
|
+
end
|
16
|
+
end
|
9
17
|
end
|
10
18
|
|
11
19
|
def teardown
|
@@ -16,11 +24,10 @@ class PrecountTest < ActiveRecord::CountLoader::TestCase
|
|
16
24
|
3
|
17
25
|
end
|
18
26
|
|
19
|
-
def
|
20
|
-
assert_equal(Tweet.
|
21
|
-
|
22
|
-
|
23
|
-
assert_queries(2) { Tweet.precount(:favs).map(&:favs_count) }
|
27
|
+
def test_precount_defines_count_loader
|
28
|
+
assert_equal(Tweet.has_reflection?(:favs_count), false)
|
29
|
+
Tweet.precount(:favs).map(&:favs_count)
|
30
|
+
assert_equal(Tweet.has_reflection?(:favs_count), true)
|
24
31
|
end
|
25
32
|
|
26
33
|
def test_precount_has_many_with_count_loader_does_not_execute_n_1_queries
|
@@ -33,6 +40,7 @@ class PrecountTest < ActiveRecord::CountLoader::TestCase
|
|
33
40
|
def test_precount_has_many_counts_properly
|
34
41
|
expected = Tweet.all.map { |t| t.favorites.count }
|
35
42
|
assert_equal(Tweet.all.map(&:favorites_count), expected)
|
43
|
+
assert_equal(Tweet.precount(:favorites).map { |t| t.favorites.count }, expected)
|
36
44
|
assert_equal(Tweet.precount(:favorites).map(&:favorites_count), expected)
|
37
45
|
end
|
38
46
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-precount
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Takashi Kokubun
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01
|
11
|
+
date: 2015-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -251,6 +251,7 @@ files:
|
|
251
251
|
- sample/public/robots.txt
|
252
252
|
- sample/vendor/assets/javascripts/.keep
|
253
253
|
- sample/vendor/assets/stylesheets/.keep
|
254
|
+
- test/cases/associations/eager_count_test.rb
|
254
255
|
- test/cases/associations/eager_load_test.rb
|
255
256
|
- test/cases/associations/includes_test.rb
|
256
257
|
- test/cases/associations/precount_test.rb
|