activerecord-precount 0.5.1 → 0.6.0
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/.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
|