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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b966bc8459e4470af32e26fa47a9b1c23c0ca2f5
4
- data.tar.gz: 8a036719d57f1a1e4978c6655473e3c6b8112219
3
+ metadata.gz: d9a9eb42c1ba767640277c85e4f3ac5253e4527b
4
+ data.tar.gz: 94b98083c83b85e45e1349c88d8560311d3c2d79
5
5
  SHA512:
6
- metadata.gz: eb7d18f68c02a4c30b33ac1b64ef44f26e430b03ca46a6fe58342220dd3c4f88b1185eab3e1c69dc34169dcbd4f6cc98979a2a7386e118369502706bade6d3dd
7
- data.tar.gz: ee694266b59423dd92a44600c9588adb26416950211c5e7ccaf8ecc00bda0253f080b73df124c37744cf0d54d08fc576f5e518863ecd6fbdf672252e4e293533
6
+ metadata.gz: c743dde55020595f377651df070394db415ea62f6ef12614d5eaca8b0bb4a5c2b6861d426bf5d87122e652511868b1c9c4d166bb26f70aa077a28e3bb7c7de82
7
+ data.tar.gz: fc1020127a928f74bf6f99846f542d469e5664a4d10a6341d6316c9759d2d912d43b49435ac65939ea22f197ac3d42c63c5bf2b7bb9267276f59a0a673a141c1
@@ -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.0
10
+ - rvm: 2.1.10
8
11
  env: TASK=test ARCONN=mysql2
9
12
  gemfile: ci/Gemfile.activerecord-4.2.x
10
- - rvm: 2.1
13
+ - rvm: 2.2.5
11
14
  env: TASK=test ARCONN=mysql2
12
- gemfile: ci/Gemfile.activerecord-4.2.x
13
- - rvm: 2.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.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-4.2.x
22
- - rvm: 2.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-4.2.x
25
- - rvm: 2.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-4.2.x
30
+ gemfile: ci/Gemfile.activerecord-5.0.x
28
31
  allow_failures:
29
32
  - env: TASK=benchmark ARCONN=mysql2
data/Gemfile CHANGED
@@ -2,5 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in activerecord-precount.gemspec
4
4
  gemspec
5
-
6
- gem 'rbench', github: 'miloops/rbench'
data/README.md CHANGED
@@ -77,9 +77,9 @@ gem 'activerecord-precount'
77
77
  ## Supported Versions
78
78
 
79
79
  - Ruby
80
- - 2.0, 2.1, 2.2
80
+ - 2.1, 2.2, 2.3
81
81
  - Rails
82
- - 4.1, 4.2
82
+ - 4.2, 5.0
83
83
  - Databases
84
84
  - sqlite
85
85
  - mysql
@@ -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.0"
20
- spec.add_runtime_dependency "activerecord", ">= 3.2.0"
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
@@ -1,54 +1,42 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../test', __FILE__)
2
2
 
3
- require 'rbench'
3
+ require 'benchmark/ips'
4
4
  require 'cases/db_config'
5
5
  require 'models/favorite'
6
6
  require 'models/tweet'
7
7
 
8
- RBench.run(50) do
9
- column :counter_cache, title: 'counter_cache'
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
- join_relation = Tweet.joins('LEFT JOIN favorites ON tweets.id = favorites.tweet_id').
19
- select('tweets.*, COUNT(favorites.id) AS joined_count').group('tweets.id')
11
+ def prepare_records(tweets_count, favorites_count)
12
+ Tweet.delete_all
13
+ Favorite.delete_all
20
14
 
21
- def prepare_records(tweets_count, favorites_count)
22
- Tweet.delete_all
23
- Favorite.delete_all
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
- test_cases = [
32
- [10, 5],
33
- [20, 20],
34
- [30, 100],
35
- [50, 10],
36
- ]
37
-
38
- test_cases.each do |tweets_count, favorites_count|
39
- prepare_records(tweets_count, favorites_count)
40
-
41
- report "N = #{tweets_count}, count = #{favorites_count}" do
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 } }
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
@@ -0,0 +1 @@
1
+ *.lock
@@ -1,4 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem 'rails', '~> 4.2.0'
3
+ gem 'rails', '~> 4.2.6'
4
4
  gemspec path: '..'
@@ -1,4 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem 'rails', '~> 4.1.0'
3
+ gem 'rails', '5.0.0.rc1'
4
4
  gemspec path: '..'
@@ -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
- if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 2
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
@@ -15,4 +15,6 @@ module ActiveRecord
15
15
  end
16
16
  end
17
17
  end
18
+
19
+ Associations::CollectionProxy.prepend(Precount::CollectionProxyExtension)
18
20
  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 HasManyExtension
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
@@ -46,4 +46,6 @@ module ActiveRecord
46
46
  end
47
47
  end
48
48
  end
49
+
50
+ Associations::JoinDependency.prepend(Precount::JoinDependencyExtension)
49
51
  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
- if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 2
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
@@ -55,4 +55,6 @@ module ActiveRecord
55
55
  end
56
56
  end
57
57
  end
58
+
59
+ Relation.prepend(Precount::RelationExtension)
58
60
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Precount
3
- VERSION = "0.5.1"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  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
- if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 2
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
 
@@ -1,6 +1,4 @@
1
1
  require 'cases/helper'
2
- require 'models/favorite'
3
- require 'models/tweet'
4
2
 
5
3
  class EagerLoadTest < ActiveRecord::CountLoader::TestCase
6
4
  def setup
@@ -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
- if ActiveRecord::VERSION::MAJOR >= 4 && ActiveRecord::VERSION::MINOR >= 2
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
 
@@ -2,3 +2,6 @@ require 'cases/db_config'
2
2
 
3
3
  require 'support/autorun'
4
4
  require 'cases/test_case'
5
+
6
+ require 'models/favorite'
7
+ require 'models/tweet'
@@ -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.5.1
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: 2015-09-09 00:00:00.000000000 Z
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: 3.2.0
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: 3.2.0
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/Gemfile.activerecord-4.1.x
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/associations/builder/count_loader.rb
192
- - lib/active_record/associations/count_loader.rb
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.0'
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.4.5
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,13 +0,0 @@
1
- module ActiveRecord::Associations::Builder
2
- class CountLoader < SingularAssociation
3
- self.valid_options = [:class, :class_name, :foreign_key]
4
-
5
- def macro
6
- :count_loader
7
- end
8
-
9
- def self.valid_dependent_options
10
- []
11
- end
12
- end
13
- end
@@ -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