activerecord-precount 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +16 -13
- data/Gemfile +0 -2
- data/README.md +2 -2
- data/activerecord-precount.gemspec +4 -3
- data/benchmark.rb +30 -42
- data/ci/.gitignore +1 -0
- data/ci/Gemfile.activerecord-4.2.x +1 -1
- data/ci/{Gemfile.activerecord-4.1.x → Gemfile.activerecord-5.0.x} +1 -1
- data/lib/active_record/precount.rb +12 -0
- data/lib/active_record/precount/association_reflection_extension.rb +45 -0
- data/lib/active_record/precount/base_extension.rb +3 -5
- data/lib/active_record/precount/collection_proxy_extension.rb +2 -0
- data/lib/active_record/precount/has_many_extension.rb +54 -1
- data/lib/active_record/precount/join_dependency_extension.rb +2 -0
- data/lib/active_record/precount/preloader_extension.rb +71 -0
- data/lib/active_record/precount/reflection_extension.rb +13 -29
- data/lib/active_record/precount/relation_extension.rb +2 -0
- data/lib/active_record/precount/version.rb +1 -1
- data/lib/activerecord-precount.rb +1 -8
- data/test/cases/associations/eager_count_test.rb +3 -7
- data/test/cases/associations/eager_load_test.rb +0 -2
- data/test/cases/associations/precount_test.rb +3 -7
- data/test/cases/helper.rb +3 -0
- data/test/schema/schema.rb +2 -2
- metadata +38 -25
- data/lib/active_record/associations/builder/count_loader.rb +0 -13
- data/lib/active_record/associations/count_loader.rb +0 -27
- data/lib/active_record/associations/preloader/count_loader.rb +0 -37
- data/lib/active_record/precount/extend.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d9a9eb42c1ba767640277c85e4f3ac5253e4527b
|
4
|
+
data.tar.gz: 94b98083c83b85e45e1349c88d8560311d3c2d79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c743dde55020595f377651df070394db415ea62f6ef12614d5eaca8b0bb4a5c2b6861d426bf5d87122e652511868b1c9c4d166bb26f70aa077a28e3bb7c7de82
|
7
|
+
data.tar.gz: fc1020127a928f74bf6f99846f542d469e5664a4d10a6341d6316c9759d2d912d43b49435ac65939ea22f197ac3d42c63c5bf2b7bb9267276f59a0a673a141c1
|
data/.travis.yml
CHANGED
@@ -2,28 +2,31 @@ script: ci/travis.rb
|
|
2
2
|
language: ruby
|
3
3
|
sudo: false
|
4
4
|
cache: bundler
|
5
|
+
branches:
|
6
|
+
only:
|
7
|
+
- master
|
5
8
|
matrix:
|
6
9
|
include:
|
7
|
-
- rvm: 2.
|
10
|
+
- rvm: 2.1.10
|
8
11
|
env: TASK=test ARCONN=mysql2
|
9
12
|
gemfile: ci/Gemfile.activerecord-4.2.x
|
10
|
-
- rvm: 2.
|
13
|
+
- rvm: 2.2.5
|
11
14
|
env: TASK=test ARCONN=mysql2
|
12
|
-
gemfile: ci/Gemfile.activerecord-
|
13
|
-
- rvm: 2.
|
15
|
+
gemfile: ci/Gemfile.activerecord-5.0.x
|
16
|
+
- rvm: 2.3.1
|
14
17
|
env: TASK=test ARCONN=mysql2
|
15
|
-
gemfile: ci/Gemfile.activerecord-4.1.x
|
16
|
-
- rvm: 2.2
|
17
|
-
env: TASK=test ARCONN=sqlite3
|
18
18
|
gemfile: ci/Gemfile.activerecord-4.2.x
|
19
|
-
- rvm: 2.
|
19
|
+
- rvm: 2.3.1
|
20
|
+
env: TASK=test ARCONN=sqlite3
|
21
|
+
gemfile: ci/Gemfile.activerecord-5.0.x
|
22
|
+
- rvm: 2.3.1
|
20
23
|
env: TASK=test ARCONN=mysql2
|
21
|
-
gemfile: ci/Gemfile.activerecord-
|
22
|
-
- rvm: 2.
|
24
|
+
gemfile: ci/Gemfile.activerecord-5.0.x
|
25
|
+
- rvm: 2.3.1
|
23
26
|
env: TASK=test ARCONN=postgresql
|
24
|
-
gemfile: ci/Gemfile.activerecord-
|
25
|
-
- rvm: 2.
|
27
|
+
gemfile: ci/Gemfile.activerecord-5.0.x
|
28
|
+
- rvm: 2.3.1
|
26
29
|
env: TASK=benchmark ARCONN=mysql2
|
27
|
-
gemfile: ci/Gemfile.activerecord-
|
30
|
+
gemfile: ci/Gemfile.activerecord-5.0.x
|
28
31
|
allow_failures:
|
29
32
|
- env: TASK=benchmark ARCONN=mysql2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,9 +16,11 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.test_files = spec.files.grep(%r{^spec/})
|
17
17
|
spec.require_paths = ["lib"]
|
18
18
|
|
19
|
-
spec.required_ruby_version = ">= 2.
|
20
|
-
spec.add_runtime_dependency "activerecord", ">=
|
19
|
+
spec.required_ruby_version = ">= 2.1"
|
20
|
+
spec.add_runtime_dependency "activerecord", ">= 4.2"
|
21
|
+
spec.add_development_dependency "benchmark-ips"
|
21
22
|
spec.add_development_dependency "minitest"
|
23
|
+
spec.add_development_dependency "minitest-line"
|
22
24
|
spec.add_development_dependency "rake"
|
23
25
|
spec.add_development_dependency "erubis"
|
24
26
|
spec.add_development_dependency "bundler"
|
@@ -26,6 +28,5 @@ Gem::Specification.new do |spec|
|
|
26
28
|
spec.add_development_dependency "sqlite3"
|
27
29
|
spec.add_development_dependency "mysql2", ">= 0.3", "< 0.4"
|
28
30
|
spec.add_development_dependency "postgres"
|
29
|
-
spec.add_development_dependency "rbench"
|
30
31
|
spec.add_development_dependency "dalli"
|
31
32
|
end
|
data/benchmark.rb
CHANGED
@@ -1,54 +1,42 @@
|
|
1
1
|
$LOAD_PATH.unshift File.expand_path('../test', __FILE__)
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'benchmark/ips'
|
4
4
|
require 'cases/db_config'
|
5
5
|
require 'models/favorite'
|
6
6
|
require 'models/tweet'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
column :left_join, title: 'LEFT JOIN'
|
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'
|
15
|
-
column :has_many, title: 'preload'
|
16
|
-
column :count_query, title: 'N+1 COUNT'
|
8
|
+
join_relation = Tweet.joins('LEFT JOIN favorites ON tweets.id = favorites.tweet_id').
|
9
|
+
select('tweets.*, COUNT(favorites.id) AS joined_count').group('tweets.id')
|
17
10
|
|
18
|
-
|
19
|
-
|
11
|
+
def prepare_records(tweets_count, favorites_count)
|
12
|
+
Tweet.delete_all
|
13
|
+
Favorite.delete_all
|
20
14
|
|
21
|
-
|
22
|
-
Tweet.
|
23
|
-
Favorite.
|
24
|
-
|
25
|
-
tweets_count.times do
|
26
|
-
t = Tweet.create(favorites_count_cache: 0)
|
27
|
-
favorites_count.times { Favorite.create(tweet: t) }
|
28
|
-
end
|
15
|
+
tweets_count.times do
|
16
|
+
t = Tweet.create(favorites_count_cache: 0)
|
17
|
+
favorites_count.times { Favorite.create(tweet: t) }
|
29
18
|
end
|
19
|
+
end
|
30
20
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
report
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
count_query { Tweet.all.map{ |t| t.favorites.count } }
|
52
|
-
end
|
21
|
+
test_cases = [
|
22
|
+
[30, 100],
|
23
|
+
]
|
24
|
+
|
25
|
+
test_cases.each do |tweets_count, favorites_count|
|
26
|
+
prepare_records(tweets_count, favorites_count)
|
27
|
+
|
28
|
+
puts "N = #{tweets_count}, count = #{favorites_count}"
|
29
|
+
Benchmark.ips do |x|
|
30
|
+
x.report('counter_cache') { Tweet.all.map(&:favorites_count_cache) }
|
31
|
+
x.report('LEFT JOIN') { Tweet.joins('LEFT JOIN favorites ON tweets.id = favorites.tweet_id').
|
32
|
+
select('tweets.*, COUNT(favorites.id) AS joined_count').
|
33
|
+
group('tweets.id').map(&:joined_count) }
|
34
|
+
x.report('eager_count') { Tweet.eager_count(:favorites).map(&:favorites_count) }
|
35
|
+
x.report('precount') { Tweet.precount(:favorites).map(&:favorites_count) }
|
36
|
+
x.report('slow eager_count') { Tweet.eager_count(:favorites).map { |t| t.favorites.count } }
|
37
|
+
x.report('slow precount') { Tweet.precount(:favorites).map { |t| t.favorites.count } }
|
38
|
+
x.report('preload') { Tweet.preload(:favorites).map{ |t| t.favorites.size } }
|
39
|
+
x.report('N+1 COUNT') { Tweet.all.map{ |t| t.favorites.count } }
|
40
|
+
x.compare!
|
53
41
|
end
|
54
42
|
end
|
data/ci/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.lock
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "active_support/lazy_load_hooks"
|
2
|
+
|
3
|
+
ActiveSupport.on_load(:active_record) do
|
4
|
+
require "active_record/precount/association_reflection_extension"
|
5
|
+
require "active_record/precount/base_extension"
|
6
|
+
require "active_record/precount/collection_proxy_extension"
|
7
|
+
require "active_record/precount/has_many_extension"
|
8
|
+
require "active_record/precount/relation_extension"
|
9
|
+
require "active_record/precount/reflection_extension"
|
10
|
+
require "active_record/precount/preloader_extension"
|
11
|
+
require "active_record/precount/join_dependency_extension"
|
12
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class CountLoader < SingularAssociation
|
4
|
+
# Not preloaded behaviour of count_loader association
|
5
|
+
# When this method is called, it will be N+1 query
|
6
|
+
def load_target
|
7
|
+
count_target = reflection.name_without_count.to_sym
|
8
|
+
@target = owner.association(count_target).count
|
9
|
+
|
10
|
+
loaded! unless loaded?
|
11
|
+
target
|
12
|
+
rescue ActiveRecord::RecordNotFound
|
13
|
+
reset
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Precount
|
19
|
+
module AssociationReflectionExtension
|
20
|
+
def klass
|
21
|
+
case macro
|
22
|
+
when :count_loader
|
23
|
+
@klass ||= active_record.send(:compute_type, options[:class_name] || name_without_count.singularize.classify)
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def name_without_count
|
30
|
+
name.to_s.sub(/_count$/, "")
|
31
|
+
end
|
32
|
+
|
33
|
+
def association_class
|
34
|
+
case macro
|
35
|
+
when :count_loader
|
36
|
+
ActiveRecord::Associations::CountLoader
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Reflection::AssociationReflection.prepend(Precount::AssociationReflectionExtension)
|
45
|
+
end
|
@@ -8,12 +8,10 @@ module ActiveRecord
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def reflection_for(name)
|
11
|
-
|
12
|
-
reflections[name.to_s]
|
13
|
-
else
|
14
|
-
reflections[name.to_sym]
|
15
|
-
end
|
11
|
+
reflections[name.to_s]
|
16
12
|
end
|
17
13
|
end
|
18
14
|
end
|
15
|
+
|
16
|
+
Base.send(:extend, Precount::BaseExtension)
|
19
17
|
end
|
@@ -1,7 +1,31 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
module Builder
|
4
|
+
class CountLoader < SingularAssociation
|
5
|
+
def self.valid_options(*)
|
6
|
+
[:class, :class_name, :foreign_key]
|
7
|
+
end
|
8
|
+
|
9
|
+
if ActiveRecord.version.segments.first >= 5
|
10
|
+
def self.macro
|
11
|
+
:count_loader
|
12
|
+
end
|
13
|
+
else
|
14
|
+
def macro
|
15
|
+
:count_loader
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.valid_dependent_options
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
2
26
|
module Precount
|
3
27
|
module Builder
|
4
|
-
module
|
28
|
+
module Rails4HasManyExtension
|
5
29
|
def valid_options
|
6
30
|
super + [:count_loader]
|
7
31
|
end
|
@@ -20,6 +44,35 @@ module ActiveRecord
|
|
20
44
|
Reflection.add_reflection(model, name_with_count, reflection)
|
21
45
|
end
|
22
46
|
end
|
47
|
+
|
48
|
+
module Rails5HasManyExtension
|
49
|
+
def valid_options(*)
|
50
|
+
super + [:count_loader]
|
51
|
+
end
|
52
|
+
|
53
|
+
def build(model, name, scope, options, &block)
|
54
|
+
if scope.is_a?(Hash)
|
55
|
+
options = scope
|
56
|
+
scope = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
if options[:count_loader]
|
60
|
+
name_with_count = :"#{name}_count"
|
61
|
+
name_with_count = options[:count_loader] if options[:count_loader].is_a?(Symbol)
|
62
|
+
|
63
|
+
valid_options = options.slice(*Associations::Builder::CountLoader.valid_options)
|
64
|
+
reflection = Associations::Builder::CountLoader.build(model, name_with_count, scope, valid_options)
|
65
|
+
Reflection.add_reflection(model, name_with_count, reflection)
|
66
|
+
end
|
67
|
+
super
|
68
|
+
end
|
69
|
+
end
|
23
70
|
end
|
24
71
|
end
|
72
|
+
|
73
|
+
if ActiveRecord.version.segments.first >= 5
|
74
|
+
Associations::Builder::HasMany.send(:extend, Precount::Builder::Rails5HasManyExtension)
|
75
|
+
else
|
76
|
+
Associations::Builder::HasMany.prepend(Precount::Builder::Rails4HasManyExtension)
|
77
|
+
end
|
25
78
|
end
|
@@ -1,4 +1,73 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class Preloader
|
4
|
+
class CountLoader < SingularAssociation
|
5
|
+
if ActiveRecord.version.segments.first >= 5
|
6
|
+
def association_key_name
|
7
|
+
reflection.foreign_key
|
8
|
+
end
|
9
|
+
|
10
|
+
def owner_key_name
|
11
|
+
reflection.active_record_primary_key
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def preload(preloader)
|
17
|
+
associated_records_by_owner(preloader).each do |owner, associated_records|
|
18
|
+
owner.association(reflection.name).target = associated_records.first.to_i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_records
|
23
|
+
return {} if owner_keys.empty?
|
24
|
+
|
25
|
+
slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
|
26
|
+
@preloaded_records = slices.flat_map { |slice|
|
27
|
+
records_for(slice)
|
28
|
+
}
|
29
|
+
|
30
|
+
Hash[@preloaded_records.first.map { |key, count| [key, [count]] }]
|
31
|
+
end
|
32
|
+
|
33
|
+
def query_scope(ids)
|
34
|
+
scope.where(association_key.in(ids)).group(association_key_name).count(association_key_name)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
def association_key_name
|
38
|
+
reflection.foreign_key
|
39
|
+
end
|
40
|
+
|
41
|
+
def owner_key_name
|
42
|
+
reflection.active_record_primary_key
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def preload(preloader)
|
48
|
+
associated_records_by_owner(preloader).each do |owner, associated_records|
|
49
|
+
owner.association(reflection.name).target = associated_records.first.to_i
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def load_slices(slices)
|
54
|
+
@preloaded_records = slices.flat_map { |slice|
|
55
|
+
records_for(slice)
|
56
|
+
}
|
57
|
+
|
58
|
+
@preloaded_records.first.map { |key, count|
|
59
|
+
[count, key]
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def query_scope(ids)
|
64
|
+
scope.where(association_key.in(ids)).group(association_key_name).count(association_key_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
2
71
|
module Precount
|
3
72
|
module PreloaderExtension
|
4
73
|
def preloader_for(reflection, owners, rhs_klass)
|
@@ -12,4 +81,6 @@ module ActiveRecord
|
|
12
81
|
end
|
13
82
|
end
|
14
83
|
end
|
84
|
+
|
85
|
+
Associations::Preloader.prepend(Precount::PreloaderExtension)
|
15
86
|
end
|
@@ -1,4 +1,14 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
module Reflection
|
3
|
+
class CountLoaderReflection < AssociationReflection
|
4
|
+
def initialize(name, scope, options, active_record)
|
5
|
+
super(name, scope, options, active_record)
|
6
|
+
end
|
7
|
+
|
8
|
+
def macro; :count_loader; end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
2
12
|
module Precount
|
3
13
|
module ReflectionExtension
|
4
14
|
def self.prepended(base)
|
@@ -11,40 +21,14 @@ module ActiveRecord
|
|
11
21
|
def create(macro, name, scope, options, ar)
|
12
22
|
case macro
|
13
23
|
when :count_loader
|
14
|
-
|
15
|
-
Reflection::CountLoaderReflection.new(name, scope, options, ar)
|
16
|
-
else
|
17
|
-
Reflection::AssociationReflection.new(macro, name, scope, options, ar)
|
18
|
-
end
|
24
|
+
Reflection::CountLoaderReflection.new(name, scope, options, ar)
|
19
25
|
else
|
20
26
|
super(macro, name, scope, options, ar)
|
21
27
|
end
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
25
|
-
|
26
|
-
module AssociationReflectionExtension
|
27
|
-
def klass
|
28
|
-
case macro
|
29
|
-
when :count_loader
|
30
|
-
@klass ||= active_record.send(:compute_type, options[:class_name] || name_without_count.singularize.classify)
|
31
|
-
else
|
32
|
-
super
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def name_without_count
|
37
|
-
name.to_s.sub(/_count$/, "")
|
38
|
-
end
|
39
|
-
|
40
|
-
def association_class
|
41
|
-
case macro
|
42
|
-
when :count_loader
|
43
|
-
ActiveRecord::Associations::CountLoader
|
44
|
-
else
|
45
|
-
super
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
31
|
end
|
32
|
+
|
33
|
+
Reflection.prepend(Precount::ReflectionExtension)
|
50
34
|
end
|
@@ -1,8 +1 @@
|
|
1
|
-
require "active_record"
|
2
|
-
require "active_support/lazy_load_hooks"
|
3
|
-
|
4
|
-
require "active_record/associations/count_loader"
|
5
|
-
require "active_record/associations/builder/count_loader"
|
6
|
-
require "active_record/associations/preloader/count_loader"
|
7
|
-
|
8
|
-
require "active_record/precount/extend"
|
1
|
+
require "active_record/precount"
|
@@ -8,17 +8,13 @@ class EagerCountTest < ActiveRecord::CountLoader::TestCase
|
|
8
8
|
Favorite.create(tweet: tweet, user_id: j + 1)
|
9
9
|
end
|
10
10
|
end
|
11
|
+
end
|
11
12
|
|
13
|
+
def teardown
|
12
14
|
if Tweet.has_reflection?(:favs_count)
|
13
|
-
|
14
|
-
Tweet._reflections.delete('favs_count')
|
15
|
-
else
|
16
|
-
Tweet._reflections.delete(:favs_count)
|
17
|
-
end
|
15
|
+
Tweet.reflections.delete('favs_count')
|
18
16
|
end
|
19
|
-
end
|
20
17
|
|
21
|
-
def teardown
|
22
18
|
[Tweet, Favorite].each(&:delete_all)
|
23
19
|
end
|
24
20
|
|
@@ -8,17 +8,13 @@ class PrecountTest < ActiveRecord::CountLoader::TestCase
|
|
8
8
|
Favorite.create(tweet: tweet, user_id: j + 1)
|
9
9
|
end
|
10
10
|
end
|
11
|
+
end
|
11
12
|
|
13
|
+
def teardown
|
12
14
|
if Tweet.has_reflection?(:favs_count)
|
13
|
-
|
14
|
-
Tweet._reflections.delete('favs_count')
|
15
|
-
else
|
16
|
-
Tweet._reflections.delete(:favs_count)
|
17
|
-
end
|
15
|
+
Tweet.reflections.delete('favs_count')
|
18
16
|
end
|
19
|
-
end
|
20
17
|
|
21
|
-
def teardown
|
22
18
|
[Tweet, Favorite].each(&:delete_all)
|
23
19
|
end
|
24
20
|
|
data/test/cases/helper.rb
CHANGED
data/test/schema/schema.rb
CHANGED
@@ -2,7 +2,7 @@ ActiveRecord::Schema.define do
|
|
2
2
|
create_table :favorites, force: true do |t|
|
3
3
|
t.integer :tweet_id
|
4
4
|
t.integer :user_id
|
5
|
-
t.timestamps
|
5
|
+
t.timestamps null: false
|
6
6
|
end
|
7
7
|
add_index :favorites, :tweet_id
|
8
8
|
|
@@ -10,7 +10,7 @@ ActiveRecord::Schema.define do
|
|
10
10
|
t.integer :in_reply_to_tweet_id
|
11
11
|
t.integer :user_id
|
12
12
|
t.integer :favorites_count_cache
|
13
|
-
t.timestamps
|
13
|
+
t.timestamps null: false
|
14
14
|
end
|
15
15
|
add_index :tweets, :in_reply_to_tweet_id
|
16
16
|
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.6.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:
|
11
|
+
date: 2016-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '4.2'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: benchmark-ips
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: minitest
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-line
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: rake
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,20 +170,6 @@ dependencies:
|
|
142
170
|
- - ">="
|
143
171
|
- !ruby/object:Gem::Version
|
144
172
|
version: '0'
|
145
|
-
- !ruby/object:Gem::Dependency
|
146
|
-
name: rbench
|
147
|
-
requirement: !ruby/object:Gem::Requirement
|
148
|
-
requirements:
|
149
|
-
- - ">="
|
150
|
-
- !ruby/object:Gem::Version
|
151
|
-
version: '0'
|
152
|
-
type: :development
|
153
|
-
prerelease: false
|
154
|
-
version_requirements: !ruby/object:Gem::Requirement
|
155
|
-
requirements:
|
156
|
-
- - ">="
|
157
|
-
- !ruby/object:Gem::Version
|
158
|
-
version: '0'
|
159
173
|
- !ruby/object:Gem::Dependency
|
160
174
|
name: dalli
|
161
175
|
requirement: !ruby/object:Gem::Requirement
|
@@ -185,15 +199,14 @@ files:
|
|
185
199
|
- Rakefile
|
186
200
|
- activerecord-precount.gemspec
|
187
201
|
- benchmark.rb
|
188
|
-
- ci
|
202
|
+
- ci/.gitignore
|
189
203
|
- ci/Gemfile.activerecord-4.2.x
|
204
|
+
- ci/Gemfile.activerecord-5.0.x
|
190
205
|
- ci/travis.rb
|
191
|
-
- lib/active_record/
|
192
|
-
- lib/active_record/
|
193
|
-
- lib/active_record/associations/preloader/count_loader.rb
|
206
|
+
- lib/active_record/precount.rb
|
207
|
+
- lib/active_record/precount/association_reflection_extension.rb
|
194
208
|
- lib/active_record/precount/base_extension.rb
|
195
209
|
- lib/active_record/precount/collection_proxy_extension.rb
|
196
|
-
- lib/active_record/precount/extend.rb
|
197
210
|
- lib/active_record/precount/has_many_extension.rb
|
198
211
|
- lib/active_record/precount/join_dependency_extension.rb
|
199
212
|
- lib/active_record/precount/preloader_extension.rb
|
@@ -285,7 +298,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
285
298
|
requirements:
|
286
299
|
- - ">="
|
287
300
|
- !ruby/object:Gem::Version
|
288
|
-
version: '2.
|
301
|
+
version: '2.1'
|
289
302
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
290
303
|
requirements:
|
291
304
|
- - ">="
|
@@ -293,7 +306,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
293
306
|
version: '0'
|
294
307
|
requirements: []
|
295
308
|
rubyforge_project:
|
296
|
-
rubygems_version: 2.
|
309
|
+
rubygems_version: 2.5.1
|
297
310
|
signing_key:
|
298
311
|
specification_version: 4
|
299
312
|
summary: N+1 count query killer for ActiveRecord
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
class CountLoader < SingularAssociation
|
4
|
-
# Not preloaded behaviour of count_loader association
|
5
|
-
# When this method is called, it will be N+1 query
|
6
|
-
def load_target
|
7
|
-
count_target = reflection.name_without_count.to_sym
|
8
|
-
@target = owner.association(count_target).count
|
9
|
-
|
10
|
-
loaded! unless loaded?
|
11
|
-
target
|
12
|
-
rescue ActiveRecord::RecordNotFound
|
13
|
-
reset
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
module Reflection
|
19
|
-
class CountLoaderReflection < AssociationReflection
|
20
|
-
def initialize(name, scope, options, active_record)
|
21
|
-
super(name, scope, options, active_record)
|
22
|
-
end
|
23
|
-
|
24
|
-
def macro; :count_loader; end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module ActiveRecord
|
2
|
-
module Associations
|
3
|
-
class Preloader
|
4
|
-
class CountLoader < SingularAssociation
|
5
|
-
def association_key_name
|
6
|
-
reflection.foreign_key
|
7
|
-
end
|
8
|
-
|
9
|
-
def owner_key_name
|
10
|
-
reflection.active_record_primary_key
|
11
|
-
end
|
12
|
-
|
13
|
-
private
|
14
|
-
|
15
|
-
def preload(preloader)
|
16
|
-
associated_records_by_owner(preloader).each do |owner, associated_records|
|
17
|
-
owner.association(reflection.name).target = associated_records.first.to_i
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def load_slices(slices)
|
22
|
-
@preloaded_records = slices.flat_map { |slice|
|
23
|
-
records_for(slice)
|
24
|
-
}
|
25
|
-
|
26
|
-
@preloaded_records.first.map { |key, count|
|
27
|
-
[count, key]
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
def query_scope(ids)
|
32
|
-
scope.where(association_key.in(ids)).group(association_key_name).count(association_key_name)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require "active_record/precount/base_extension"
|
2
|
-
require "active_record/precount/collection_proxy_extension"
|
3
|
-
require "active_record/precount/has_many_extension"
|
4
|
-
require "active_record/precount/join_dependency_extension"
|
5
|
-
require "active_record/precount/preloader_extension"
|
6
|
-
require "active_record/precount/reflection_extension"
|
7
|
-
require "active_record/precount/relation_extension"
|
8
|
-
|
9
|
-
ActiveSupport.on_load(:active_record) do
|
10
|
-
module ActiveRecord
|
11
|
-
Base.send(:extend, Precount::BaseExtension)
|
12
|
-
Relation.send(:prepend, Precount::RelationExtension)
|
13
|
-
Reflection.send(:prepend, Precount::ReflectionExtension)
|
14
|
-
Associations::Preloader.send(:prepend, Precount::PreloaderExtension)
|
15
|
-
Associations::JoinDependency.send(:prepend, Precount::JoinDependencyExtension)
|
16
|
-
Associations::CollectionProxy.send(:prepend, Precount::CollectionProxyExtension)
|
17
|
-
Associations::Builder::HasMany.send(:prepend, Precount::Builder::HasManyExtension)
|
18
|
-
Reflection::AssociationReflection.send(:prepend, Precount::AssociationReflectionExtension)
|
19
|
-
end
|
20
|
-
end
|