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 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