activerecord-has_count 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1638ba180bf14cd976c29912e38505fb21b73a4b
4
+ data.tar.gz: dd52048a4744eb4f5b740b0b0df9ca74f8a8eb99
5
+ SHA512:
6
+ metadata.gz: c3e3313087b1e78e6ef21b26c938a4ebe17124c74ac9c596970f4614e329f7f89375f1bdf3c2586b6dcd068f0e3ed705ccb38526ddb8b2d59e7635d6e8179930
7
+ data.tar.gz: 8128b246278b8c678a7fd8a52588e570444555e8e5362a0636cdb86170b77e92c8e2a3695035d393daa485ef7aad6d812ef62ddd923c64ce8065f1d9500bbcec
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord-has_count.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Takashi Kokubun
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # ActiveRecord::HasCount
2
+
3
+ N+1 count query killer for ActiveRecord
4
+ ActiveRecord::HasCount allows you to cache count of associated records by eager loading
5
+
6
+ ## Why ActiveRecord::HasCount?
7
+ Rails provides a way to resolve N+1 count query, which is [belongs\_to's counter\_cache option](http://guides.rubyonrails.org/association_basics.html#counter-cache).
8
+ It requires a column to cache the count. But adding a column just for count cache is overkill.
9
+
10
+ Thus this plugin enables you to preload counts in the same way as `has_many` and `belongs_to`.
11
+ `has_count` is an ActiveRecord's association, which is preloadable by `preload` or `includes`.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'activerecord-has_count'
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### Add count\_preloadable scope
24
+ First, call `has_count` with an association whose count you want to preload
25
+
26
+ ```rb
27
+ class Tweet
28
+ has_many :replies
29
+ has_count :replies # defines association named :replies_count
30
+ end
31
+ ```
32
+
33
+ The option creates an additional association whose name is `replies_count`.
34
+ Its association type is not an ordinary one (i.e. `has_many`, `belongs_to`) but `has_count`.
35
+
36
+ ### Preload the association
37
+ This association works well by default.
38
+
39
+ ```rb
40
+ @tweets = Tweet.all
41
+ @tweets.each do |tweet|
42
+ p tweets.replies_count # same as tweets.replies.count
43
+ end
44
+ ```
45
+
46
+ You can eagerly load `has_count` association by `includes` or `preload`.
47
+
48
+ ```rb
49
+ @tweets = Tweet.preload(:replies_count)
50
+ @tweets.each do |tweet|
51
+ p tweets.replies_count # this line doesn't execute an additional query
52
+ end
53
+ ```
54
+
55
+ Since it is association, you can preload nested `has_count` association.
56
+
57
+ ```rb
58
+ @favorites = Favorite.preload(tweet: :replies_count)
59
+ @favorites.each do |favorite|
60
+ p favorite.tweet.replies_count # this line doesn't execute an additional query
61
+ end
62
+ ```
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it ( https://github.com/k0kubun/has_count/fork )
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'active_record/has_count/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "activerecord-has_count"
7
+ spec.version = ActiveRecord::HasCount::VERSION
8
+ spec.authors = ["Takashi Kokubun"]
9
+ spec.email = ["takashikkbn@gmail.com"]
10
+ spec.summary = %q{N+1 count query killer for ActiveRecord}
11
+ spec.description = %q{N+1 count query killer for ActiveRecord}
12
+ spec.homepage = "https://github.com/k0kubun/activerecord-has_count"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.test_files = spec.files.grep(%r{^spec/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.required_ruby_version = ">= 1.9.2"
20
+ spec.add_runtime_dependency "activerecord", ">= 3.0"
21
+ spec.add_development_dependency "rspec", "~> 3.0.0"
22
+ spec.add_development_dependency "factory_girl", "~> 4.2.0"
23
+ spec.add_development_dependency "sqlite3"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "bundler"
26
+ spec.add_development_dependency "pry"
27
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveRecord::Associations::Builder
2
+ class HasCount < SingularAssociation
3
+ def macro
4
+ :has_count
5
+ end
6
+
7
+ def valid_options
8
+ []
9
+ end
10
+
11
+ def self.valid_dependent_options
12
+ []
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ module ClassMethods
4
+ module HasCount
5
+ private
6
+
7
+ def has_count(name, scope = nil, options = {}, &extension)
8
+ name_with_count = :"#{name}_count"
9
+
10
+ reflection = Builder::HasCount.build(self, name_with_count, scope, options, &extension)
11
+ Reflection.add_reflection(self, name_with_count, reflection)
12
+ end
13
+ end
14
+ end
15
+
16
+ class HasCount < SingularAssociation
17
+ # Not preloaded behaviour of count_preloadable association
18
+ # When this method is called, it will be N+1 query
19
+ def load_target
20
+ count_target = name_without_count.to_sym
21
+ @target = owner.association(count_target).count
22
+
23
+ loaded! unless loaded?
24
+ target
25
+ rescue ActiveRecord::RecordNotFound
26
+ reset
27
+ end
28
+
29
+ # This method is used by preloader when grouping preload target's class
30
+ def klass
31
+ reflection.active_record.send(:compute_type, name_without_count.singularize.classify)
32
+ end
33
+
34
+ def name_without_count
35
+ reflection.name.to_s.sub(/_count$/, "")
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveRecord
2
+ # This imitates EagerLoadPolymorphicError
3
+ class EagerLoadHasCountError < ActiveRecordError
4
+ def initialize(reflection)
5
+ super("Cannot eagerly load the has_count association #{reflection.name.inspect}")
6
+ end
7
+ end
8
+
9
+ module Associations
10
+ class JoinDependency
11
+ module HasCount
12
+ def build_with_has_count(associations, base_klass)
13
+ associations.map do |name, right|
14
+ reflection = find_reflection base_klass, name
15
+ if reflection.macro == :has_count
16
+ raise EagerLoadHasCountError.new(reflection)
17
+ end
18
+ end
19
+
20
+ build_without_has_count(associations, base_klass)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasCount < 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
+ count = associated_records.count
18
+
19
+ association = owner.association(reflection.name)
20
+ association.target = count
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def preloader_for_with_has_count(reflection, owners, rhs_klass)
28
+ preloader = preloader_for_without_has_count(reflection, owners, rhs_klass)
29
+ return preloader if preloader
30
+
31
+ case reflection.macro
32
+ when :has_count
33
+ HasCount
34
+ end
35
+ end
36
+ alias_method_chain :preloader_for, :has_count
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module ActiveRecord
2
+ module HasCount
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ module ActiveRecord
2
+ module Reflection
3
+ module HasCount
4
+ def create_with_has_count(macro, name, scope, options, ar)
5
+ case macro
6
+ when :has_count
7
+ AssociationReflection.new(macro, name, scope, options, ar)
8
+ else
9
+ create_without_has_count(macro, name, scope, options, ar)
10
+ end
11
+ end
12
+ end
13
+
14
+ class AssociationReflection
15
+ module HasCount
16
+ def association_class_with_has_count
17
+ case macro
18
+ when :has_count
19
+ ActiveRecord::Associations::HasCount
20
+ else
21
+ association_class_without_has_count
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ require "active_record/associations/has_count"
2
+ require "active_record/associations/builder/has_count"
3
+ require "active_record/associations/preloader/has_count"
4
+ require "active_record/associations/join_dependency/has_count"
5
+ require "active_record/reflection/has_count"
6
+
7
+ module ActiveRecord
8
+ module Associations
9
+ module ClassMethods
10
+ include HasCount
11
+ end
12
+ end
13
+ end
14
+
15
+ module ActiveRecord
16
+ module Reflection
17
+ class << self
18
+ include HasCount
19
+ alias_method_chain :create, :has_count
20
+ end
21
+
22
+ class AssociationReflection
23
+ include HasCount
24
+ alias_method_chain :association_class, :has_count
25
+ end
26
+ end
27
+ end
28
+
29
+ module ActiveRecord
30
+ module Associations
31
+ class JoinDependency
32
+ include HasCount
33
+ alias_method_chain :build, :has_count
34
+ end
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-has_count
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Takashi Kokubun
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 3.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 3.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: factory_girl
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 4.2.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 4.2.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
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'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: N+1 count query killer for ActiveRecord
112
+ email:
113
+ - takashikkbn@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - activerecord-has_count.gemspec
124
+ - lib/active_record/associations/builder/has_count.rb
125
+ - lib/active_record/associations/has_count.rb
126
+ - lib/active_record/associations/join_dependency/has_count.rb
127
+ - lib/active_record/associations/preloader/has_count.rb
128
+ - lib/active_record/has_count/version.rb
129
+ - lib/active_record/reflection/has_count.rb
130
+ - lib/activerecord-has_count.rb
131
+ homepage: https://github.com/k0kubun/activerecord-has_count
132
+ licenses:
133
+ - MIT
134
+ metadata: {}
135
+ post_install_message:
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - '>='
142
+ - !ruby/object:Gem::Version
143
+ version: 1.9.2
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - '>='
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubyforge_project:
151
+ rubygems_version: 2.0.3
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: N+1 count query killer for ActiveRecord
155
+ test_files: []
156
+ has_rdoc: