activerecord-precounter 0.2.3 → 0.4.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
- 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