activerecord-precounter 0.2.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +42 -0
- data/activerecord-precounter.gemspec +2 -2
- data/ci/Gemfile.activerecord-5.2.x +4 -0
- data/lib/active_record/precountable.rb +20 -0
- data/lib/active_record/precounter.rb +26 -13
- data/lib/active_record/precounter/version.rb +1 -1
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a80cb8c2919851956774b0c573d3a3271a9b4f017e33edca6fae411f1c4c189b
|
4
|
+
data.tar.gz: 9d479d623ceacf1a93ad4cc64b6ad58ce32f586e07dc8b6335d9e380f82e7d9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae135551e4619a121e488e62517c072b3692dd8b717867b45114ffe904ee50ad6cec6f3924755f511a48f640510d902986be83e0205b5b7178a054eae6d9b47d
|
7
|
+
data.tar.gz: 295507cafb1c6de872f92163d74ebfd37bae295cb2fe4478b3c313b967c0faf657090596fb37153a5a32ac5201be6c48b2384d697ca8901c98f4b0f1fe79da42
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -5,11 +5,18 @@ cache: bundler
|
|
5
5
|
branches:
|
6
6
|
only:
|
7
7
|
- master
|
8
|
+
dist: trusty
|
8
9
|
matrix:
|
9
10
|
include:
|
11
|
+
- rvm: 2.4.1
|
12
|
+
env: TASK=spec ARCONN=postgresql
|
13
|
+
gemfile: ci/Gemfile.activerecord-5.0.x
|
10
14
|
- rvm: 2.4.1
|
11
15
|
env: TASK=spec ARCONN=sqlite3
|
12
16
|
gemfile: ci/Gemfile.activerecord-5.1.x
|
13
17
|
- rvm: 2.4.1
|
14
18
|
env: TASK=spec ARCONN=postgresql
|
15
19
|
gemfile: ci/Gemfile.activerecord-5.1.x
|
20
|
+
- rvm: 2.4.1
|
21
|
+
env: TASK=spec ARCONN=postgresql
|
22
|
+
gemfile: ci/Gemfile.activerecord-5.2.x
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
## v0.4.0
|
2
|
+
|
3
|
+
* [breaking] Raise `NotPrecountedError` instead of returning nil if `*_count` is called without precount
|
4
|
+
* Add `ActiveRecord::Precountable` module to actively define `*_count` readers and writers by `precounts` DSL on ActiveRecord::Base.
|
5
|
+
|
6
|
+
## v0.3.3
|
7
|
+
|
8
|
+
* Support passing a single record to `ActiveRecord::Precounter.new` [#8](https://github.com/k0kubun/activerecord-precounter/pull/8)
|
9
|
+
|
10
|
+
## v0.3.2
|
11
|
+
|
12
|
+
* Support primary\_key option of belongs\_to association [#7](https://github.com/k0kubun/activerecord-precounter/pull/7)
|
13
|
+
|
14
|
+
## v0.3.1
|
15
|
+
|
16
|
+
* Fix [#5](https://github.com/k0kubun/activerecord-precounter/pull/5) for ActiveRecord 5.0 (take 3)
|
17
|
+
|
18
|
+
## v0.3.0
|
19
|
+
|
20
|
+
* Fix [#5](https://github.com/k0kubun/activerecord-precounter/pull/5) for ActiveRecord 5.0 (take 2)
|
21
|
+
* [breaking] Drop ActiveRecord 4.2 support
|
22
|
+
|
23
|
+
## v0.2.3
|
24
|
+
|
25
|
+
* Fix count of scoped association [#5](https://github.com/k0kubun/activerecord-precounter/pull/5)
|
26
|
+
|
27
|
+
## v0.2.2
|
28
|
+
|
29
|
+
* Fix unexpected subquery [#4](https://github.com/k0kubun/activerecord-precounter/pull/5)
|
30
|
+
|
31
|
+
## v0.2.1
|
32
|
+
|
33
|
+
* Add support for counting scoped association
|
34
|
+
* Return empty array when target records don't exist
|
35
|
+
|
36
|
+
## v0.2.0
|
37
|
+
|
38
|
+
* [breaking] Completely change interface
|
39
|
+
|
40
|
+
## v0.1.0
|
41
|
+
|
42
|
+
* Initial release
|
@@ -21,10 +21,10 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ['lib']
|
23
23
|
|
24
|
-
spec.add_dependency 'activerecord'
|
24
|
+
spec.add_dependency 'activerecord', '>= 5'
|
25
25
|
spec.add_development_dependency 'bundler'
|
26
26
|
spec.add_development_dependency 'mysql2'
|
27
|
-
spec.add_development_dependency '
|
27
|
+
spec.add_development_dependency 'pg'
|
28
28
|
spec.add_development_dependency 'rake'
|
29
29
|
spec.add_development_dependency 'rspec'
|
30
30
|
spec.add_development_dependency 'sqlite3'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Precountable
|
3
|
+
class NotPrecountedError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
def precounts(*association_names)
|
7
|
+
association_names.each do |association_name|
|
8
|
+
var_name = "#{association_name}_count"
|
9
|
+
instance_var_name = "@#{var_name}"
|
10
|
+
|
11
|
+
attr_writer(var_name)
|
12
|
+
define_method(var_name) do
|
13
|
+
count = instance_variable_get(instance_var_name)
|
14
|
+
raise NotPrecountedError.new("`#{association_name}' not precounted") unless count
|
15
|
+
count
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_record/precounter/version'
|
2
|
+
require 'active_record/precountable'
|
2
3
|
|
3
4
|
module ActiveRecord
|
4
5
|
class Precounter
|
@@ -12,33 +13,44 @@ module ActiveRecord
|
|
12
13
|
# @param [Array<String,Symbol>] association_names - Eager loaded association names. e.g. `[:users, :likes]`
|
13
14
|
# @return [Array<ActiveRecord::Base>]
|
14
15
|
def precount(*association_names)
|
15
|
-
|
16
|
-
return
|
16
|
+
# Allow single record instances as well as relations to be passed.
|
17
|
+
# The splat here will return an array of the single record if it's
|
18
|
+
# not a relation or otherwise return the records themselves via #to_a.
|
19
|
+
records = *@relation
|
20
|
+
return records if records.empty?
|
21
|
+
|
22
|
+
# We need to get the relation's active class, which is the class itself
|
23
|
+
# in the case of a single record.
|
24
|
+
klass = @relation.respond_to?(:klass) ? @relation.klass : @relation.class
|
17
25
|
|
18
26
|
association_names.each do |association_name|
|
19
27
|
association_name = association_name.to_s
|
20
|
-
reflection =
|
28
|
+
reflection = klass.reflections.fetch(association_name)
|
21
29
|
|
22
30
|
if reflection.inverse_of.nil?
|
23
31
|
raise MissingInverseOf.new(
|
24
|
-
"`#{reflection.klass}` does not have inverse of `#{
|
25
|
-
"Probably missing to call `#{reflection.klass}.belongs_to #{
|
32
|
+
"`#{reflection.klass}` does not have inverse of `#{klass}##{reflection.name}`. "\
|
33
|
+
"Probably missing to call `#{reflection.klass}.belongs_to #{klass.name.underscore.to_sym.inspect}`?"
|
26
34
|
)
|
27
35
|
end
|
28
36
|
|
37
|
+
primary_key = reflection.inverse_of.association_primary_key.to_sym
|
38
|
+
|
29
39
|
count_by_id = if reflection.has_scope?
|
30
|
-
|
40
|
+
# ActiveRecord 5.0 unscopes #scope_for argument, so adding #where outside that:
|
41
|
+
# https://github.com/rails/rails/blob/v5.0.7/activerecord/lib/active_record/reflection.rb#L314-L316
|
42
|
+
reflection.scope_for(reflection.klass.unscoped).where(reflection.inverse_of.name => records.map(&primary_key)).group(
|
31
43
|
reflection.inverse_of.foreign_key
|
32
44
|
).count
|
33
45
|
else
|
34
|
-
reflection.klass.where(reflection.inverse_of.name => records.map(
|
46
|
+
reflection.klass.where(reflection.inverse_of.name => records.map(&primary_key)).group(
|
35
47
|
reflection.inverse_of.foreign_key
|
36
48
|
).count
|
37
49
|
end
|
38
50
|
|
39
|
-
writer = define_count_accessor(
|
51
|
+
writer = define_count_accessor(klass, association_name)
|
40
52
|
records.each do |record|
|
41
|
-
record.public_send(writer, count_by_id.fetch(record.
|
53
|
+
record.public_send(writer, count_by_id.fetch(record.public_send(primary_key), 0))
|
42
54
|
end
|
43
55
|
end
|
44
56
|
records
|
@@ -46,15 +58,16 @@ module ActiveRecord
|
|
46
58
|
|
47
59
|
private
|
48
60
|
|
49
|
-
# @param [
|
61
|
+
# @param [Class] record class
|
50
62
|
# @param [String] association_name
|
51
63
|
# @return [String] writer method name
|
52
|
-
def define_count_accessor(
|
64
|
+
def define_count_accessor(klass, association_name)
|
53
65
|
reader_name = "#{association_name}_count"
|
54
66
|
writer_name = "#{reader_name}="
|
55
67
|
|
56
|
-
if !
|
57
|
-
|
68
|
+
if !klass.method_defined?(reader_name) && !klass.method_defined?(writer_name)
|
69
|
+
klass.extend(ActiveRecord::Precountable)
|
70
|
+
klass.public_send(:precounts, association_name)
|
58
71
|
end
|
59
72
|
|
60
73
|
writer_name
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-precounter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Takashi Kokubun
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5'
|
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: '5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: pg
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -118,6 +118,7 @@ files:
|
|
118
118
|
- ".gitignore"
|
119
119
|
- ".rspec"
|
120
120
|
- ".travis.yml"
|
121
|
+
- CHANGELOG.md
|
121
122
|
- Gemfile
|
122
123
|
- LICENSE.txt
|
123
124
|
- README.md
|
@@ -128,7 +129,9 @@ files:
|
|
128
129
|
- ci/Gemfile.activerecord-4.2.x
|
129
130
|
- ci/Gemfile.activerecord-5.0.x
|
130
131
|
- ci/Gemfile.activerecord-5.1.x
|
132
|
+
- ci/Gemfile.activerecord-5.2.x
|
131
133
|
- ci/travis.rb
|
134
|
+
- lib/active_record/precountable.rb
|
132
135
|
- lib/active_record/precounter.rb
|
133
136
|
- lib/active_record/precounter/version.rb
|
134
137
|
- lib/activerecord-precounter.rb
|
@@ -151,8 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
154
|
- !ruby/object:Gem::Version
|
152
155
|
version: '0'
|
153
156
|
requirements: []
|
154
|
-
|
155
|
-
rubygems_version: 2.6.13
|
157
|
+
rubygems_version: 3.2.0.pre1
|
156
158
|
signing_key:
|
157
159
|
specification_version: 4
|
158
160
|
summary: Yet Another N+1 COUNT Query Killer for ActiveRecord
|