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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c3c08d0e856aad769711131f6e7276b777e2f6cd
4
- data.tar.gz: 6648ad8319c9bd466b0693b2d3386c19f74e2d37
2
+ SHA256:
3
+ metadata.gz: a80cb8c2919851956774b0c573d3a3271a9b4f017e33edca6fae411f1c4c189b
4
+ data.tar.gz: 9d479d623ceacf1a93ad4cc64b6ad58ce32f586e07dc8b6335d9e380f82e7d9b
5
5
  SHA512:
6
- metadata.gz: 966efeff214aa006d5d272afd5197d80a64f93df982ddd8e0302b0326c182024961d50cc9f519f79821f5da651a5d2a23f8ae7e9a11efee663cf0a5bd5199891
7
- data.tar.gz: 9df24ac11ae3cfdd91037422b94e4fd3e9b40da5e21fee2a1cb6c42f3651891f389816537e81154ab56d74f5d9b1016f4ec87a7ece58a8901bad60e60955b139
6
+ metadata.gz: ae135551e4619a121e488e62517c072b3692dd8b717867b45114ffe904ee50ad6cec6f3924755f511a48f640510d902986be83e0205b5b7178a054eae6d9b47d
7
+ data.tar.gz: 295507cafb1c6de872f92163d74ebfd37bae295cb2fe4478b3c313b967c0faf657090596fb37153a5a32ac5201be6c48b2384d697ca8901c98f4b0f1fe79da42
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+ *.sqlite3
@@ -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
@@ -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 'postgres'
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,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'rails', '~> 5.2.0'
4
+ gemspec path: '..'
@@ -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
- records = @relation.to_a
16
- return [] if records.empty?
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 = @relation.klass.reflections.fetch(association_name)
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 `#{@relation.klass}##{reflection.name}`. "\
25
- "Probably missing to call `#{reflection.klass}.belongs_to #{@relation.name.underscore.to_sym.inspect}`?"
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
- reflection.scope_for(reflection.klass).where(reflection.inverse_of.name => records.map(&:id)).group(
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(&:id)).group(
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(records.first, association_name)
51
+ writer = define_count_accessor(klass, association_name)
40
52
  records.each do |record|
41
- record.public_send(writer, count_by_id.fetch(record.id, 0))
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 [ActiveRecord::Base] record
61
+ # @param [Class] record class
50
62
  # @param [String] association_name
51
63
  # @return [String] writer method name
52
- def define_count_accessor(record, association_name)
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 !record.respond_to?(reader_name) && !record.respond_to?(writer_name)
57
- record.class.send(:attr_accessor, reader_name)
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
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  class Precounter
3
- VERSION = '0.2.3'
3
+ VERSION = '0.4.0'
4
4
  end
5
5
  end
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.2.3
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: 2018-11-06 00:00:00.000000000 Z
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: '0'
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: '0'
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: postgres
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
- rubyforge_project:
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