preload_counts 0.0.3 → 0.0.4

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: df7e7e5e2c7c650635bde6f479c71b9ade401b08
4
- data.tar.gz: 3ce8526debbc0c8cd1aec23416bfd265b525cc96
3
+ metadata.gz: 08d789c6a318d6934f7d40b3af28308bcc72f99a
4
+ data.tar.gz: 49116bb125a4e84ed0b0fd50e1d7d509839f913f
5
5
  SHA512:
6
- metadata.gz: f2363d843cb7e77813f4d79918aafe6ff5ff7e53b046faa12bf3fdc3257ae54dc568f812507390891dec2e19e2d79354e8e7b6cad80013bc636088d3127e938b
7
- data.tar.gz: 41e37ad43b98d39c9001254840684b2fb5bf96b200b6b63771ff13b5f814214dfbd1473655240b326131f7c1e30818f7734da91e02061360b14685c05a405898
6
+ metadata.gz: b7768313608e32515cd120f442056455c59f43d5df190313b30ba69100ba2e32ad3780434f9c873c0a28b211c79d77b3149d2bb3552af498f2653f6be2283e05
7
+ data.tar.gz: c50fbead90d424477e048cf354e74fe4a7326e335bc893e168518e2382802ae8e454af890db934cbc1a6daa43d97b0fbff31622a59bb6b40fe4a67648286a7bd
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use ree-1.8.7@preload_counts
1
+ rvm use 2.2.0@preload_counts --create
@@ -0,0 +1,14 @@
1
+ env:
2
+ - "ACTIVERECORD_VERSION=4.2"
3
+ - "ACTIVERECORD_VERSION=4.1"
4
+ - "ACTIVERECORD_VERSION=4.0"
5
+ - "ACTIVERECORD_VERSION=master"
6
+
7
+ rvm:
8
+ - 2.0.0
9
+ - 2.1.2
10
+ - 2.2.0
11
+ - ruby-head
12
+ - jruby-19mode
13
+ - rbx
14
+
data/Gemfile CHANGED
@@ -2,3 +2,20 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in preload_counts.gemspec
4
4
  gemspec
5
+
6
+ activerecord_version = ENV["ACTIVERECORD_VERSION"] || "default"
7
+
8
+ activerecord_version = case activerecord_version
9
+ when "master"
10
+ {github: "rails/rails"}
11
+ when "default"
12
+ ">= 3.2.0"
13
+ else
14
+ "~> #{activerecord_version}"
15
+ end
16
+
17
+
18
+ gem "activerecord", activerecord_version
19
+ gem "pry"
20
+ gem "awesome_print"
21
+ gem "rake"
@@ -0,0 +1,85 @@
1
+ [![Build Status](https://travis-ci.org/smathieu/preload_counts.png?branch=master)](https://travis-ci.org/smathieu/preload_counts)
2
+
3
+ # Preload Counts
4
+
5
+ Did you ever write an index page that only needs the count of an association? Consider the following example:
6
+
7
+
8
+ ```ruby
9
+ # Models
10
+ class Post < ActiveRecord::Base
11
+ has_many :comments
12
+ end
13
+
14
+ class Comment < ActiveRecord::Base
15
+ belongs_to :post
16
+
17
+ named_scope :by_moderators, lambda { {:conditions => {:by_moderator => true} }
18
+ end
19
+
20
+ # Controller
21
+ def index
22
+ @posts = Post.all
23
+ end
24
+
25
+ # View
26
+ <% @posts.each do |post| %>
27
+ <%= post.name %>
28
+ (<%= post.comments.count %>)
29
+ (<%= post.comments.by_moderators.count %>)
30
+ <% end %>
31
+ ```
32
+
33
+ This will create two count request to the database for each post each time the view
34
+ is rendered. This is really slow. Preload counts helps you minimize the number of
35
+ calls to the database with minimal code change.
36
+
37
+ ## Usage
38
+
39
+ ```ruby
40
+ # Model
41
+ class Post < ActiveRecord::Base
42
+ # create a named_scope to preload the comments count as well as the comments
43
+ # by_moderators.
44
+ preload_counts :comments => [:by_moderators]
45
+
46
+ # preload_counts :comments would have only, preloaded the comments which
47
+ # would have been slightly faster. You can preload any number of scopes, but
48
+ # the more to preload, the more complex and slow the request will be.
49
+ has_many :comments
50
+ end
51
+
52
+ # Controller
53
+ def index
54
+ # Tell AR to preload the counts in the select request. If you don't specify
55
+ # this, the behaviour is to fallback to the slow count.
56
+ @posts = Post.preload_comment_counts
57
+ end
58
+
59
+ # View
60
+ # You have different accessor to get to the count. Beware of methods like
61
+ # .empty? that has an internal call to count.
62
+ <% @posts.each do |post| %>
63
+ <%= post.name %>
64
+ (<%= post.comments_count %>)
65
+ (<%= post.by_moderators_comments_count %>)
66
+ <% end %>
67
+ ```
68
+
69
+ ## Benchmark
70
+ More in depth benchmarking is needed, but here's sone annecdotal evidence.
71
+
72
+ Without: 650ms per request
73
+ With: 450ms per request
74
+
75
+ That's right, this saved me 200ms per request with very minimal code change.
76
+
77
+ ## Support
78
+ This has been tested on Rails 2.3.12, 3.1.0 and 4.0.2.
79
+
80
+ ## Help
81
+ ### My requests are still slow?
82
+
83
+ It's possible that this isn't the fix you need. Sometimes multiple small requests might be faster than one large request. Always benchmark your page before using this gem.
84
+
85
+ Another thing you might want to look into is adding an index to speed up the query that is being generated by preload_counts.
@@ -1,9 +1,9 @@
1
- # This adds a scope to preload the counts of an association in one SQL query.
1
+ # This adds a scope to preload the counts of an association in one SQL query.
2
2
  #
3
3
  # Consider the following code:
4
4
  # Service.all.each{|s| puts s.incidents.acknowledged.count}
5
5
  #
6
- # Each time count is called, a db query is made to fetch the count.
6
+ # Each time count is called, a db query is made to fetch the count.
7
7
  #
8
8
  # Adding this to the Service class:
9
9
  #
@@ -28,22 +28,22 @@ module PreloadCounts
28
28
  singleton.send :define_method, name do
29
29
  sql = ["#{table_name}.*"] + scopes_to_select(association, scopes)
30
30
  sql = sql.join(', ')
31
- scoped(:select => sql)
31
+ all.select(sql)
32
32
  end
33
33
 
34
34
  scopes.each do |scope|
35
35
  # Define accessor for each count
36
36
  accessor_name = find_accessor_name(association, scope)
37
37
  define_method accessor_name do
38
- result = send(association)
39
- result = result.send(scope) if scope
38
+ result = public_send(association)
39
+ result = result.public_send(scope) if scope
40
40
  (self[accessor_name] || result.size).to_i
41
41
  end
42
42
  end
43
43
 
44
44
  end
45
45
  end
46
-
46
+
47
47
  private
48
48
  def scopes_to_select(association, scopes)
49
49
  scopes.map do |scope|
@@ -56,25 +56,21 @@ module PreloadCounts
56
56
  conditions = []
57
57
 
58
58
  if scope
59
- if ActiveRecord::VERSION::MAJOR < 3
60
- scope_sql = resolved_association.scopes[scope].call(resolved_association).send(:construct_finder_sql, {})
61
- else
62
- scope_sql = resolved_association.send(scope).to_sql
63
- end
59
+ scope_sql = resolved_association.send(scope).to_sql
64
60
  condition = scope_sql.gsub(/^.*WHERE/, '')
65
61
  conditions << condition
66
62
  end
67
63
 
68
- association_condition = self.reflections[association].options[:conditions]
69
- conditions << association_condition if association_condition
64
+ r_scope = self.reflections.with_indifferent_access[association].scope
65
+ if r_scope
66
+ conditions += self.instance_eval(&r_scope).where_values
67
+ end
70
68
 
71
- # FIXME This is a really hacking way of getting the named_scope condition.
72
- # In Rails 3 we would have AREL to get to it.
73
69
  sql = <<-SQL
74
- (SELECT count(*)
75
- FROM #{association}
76
- WHERE #{association}.#{table_name.singularize}_id = #{table_name}.id AND
77
- #{conditions_to_sql conditions}) as #{find_accessor_name(association, scope)}
70
+ (SELECT count(*)
71
+ FROM #{association}
72
+ WHERE #{association}.#{table_name.singularize}_id = #{table_name}.id AND
73
+ #{conditions_to_sql conditions}) AS #{find_accessor_name(association, scope)}
78
74
  SQL
79
75
  end
80
76
 
@@ -1,3 +1,3 @@
1
1
  module PreloadCounts
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
- s.add_development_dependency "rspec"
22
- s.add_development_dependency "activerecord", "~> 3.2.0"
21
+ s.add_dependency "activerecord", "> 3.2.0"
22
+ s.add_development_dependency "rspec", "~> 2.0"
23
23
  s.add_development_dependency "sqlite3"
24
24
  end
@@ -1,12 +1,10 @@
1
1
  require 'spec_helper'
2
2
  require 'logger'
3
3
 
4
- if ActiveRecord::VERSION::MAJOR < 3
5
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
6
- else
7
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
8
- ActiveRecord::Base.logger = Logger.new(nil)
9
- end
4
+ puts "Using ActiveRecord #{ActiveRecord::VERSION::STRING}"
5
+
6
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
7
+ ActiveRecord::Base.logger = Logger.new(nil)
10
8
 
11
9
  def setup_db
12
10
  ActiveRecord::Schema.define(:version => 1) do
@@ -14,40 +12,36 @@ def setup_db
14
12
  end
15
13
 
16
14
  create_table :comments do |t|
17
- t.integer :post_id, :null => false
15
+ t.integer :post_id, :null => false
18
16
  t.datetime :deleted_at
19
17
  end
20
18
  end
21
19
  end
22
20
 
23
- setup_db
21
+ setup_db
24
22
 
25
23
  class Post < ActiveRecord::Base
26
24
  has_many :comments
27
- has_many :active_comments, :conditions => "deleted_at IS NULL", :class_name => 'Comment'
25
+ has_many :active_comments, -> { where( "deleted_at IS NULL") }, :class_name => 'Comment'
28
26
  preload_counts :comments => [:with_even_id]
29
27
  preload_counts :active_comments
30
28
  end
31
29
 
32
30
  class PostWithActiveComments < ActiveRecord::Base
33
- set_table_name :posts
31
+ self.table_name = :posts
34
32
 
35
- has_many :comments, :conditions => "deleted_at IS NULL"
33
+ has_many :comments, -> { where "deleted_at IS NULL" }
36
34
  preload_counts :comments
37
35
  end
38
36
 
39
37
  class Comment < ActiveRecord::Base
40
38
  belongs_to :post
41
39
 
42
- if ActiveRecord::VERSION::MAJOR < 3
43
- named_scope :with_even_id, lambda { {:conditions => "comments.id % 2 == 0"} }
44
- else
45
- scope :with_even_id, where('id % 2 = 0')
46
- end
40
+ scope :with_even_id, -> { where('id % 2 = 0') }
47
41
  end
48
42
 
49
43
  def create_data
50
- post = Post.create
44
+ post = Post.create
51
45
  5.times { post.comments.create }
52
46
  5.times { post.comments.create :deleted_at => Time.now }
53
47
  end
@@ -56,14 +50,14 @@ create_data
56
50
 
57
51
  describe Post do
58
52
  it "should have a preload_comment_counts scope" do
59
- Post.should respond_to(:preload_comment_counts)
53
+ Post.should respond_to(:preload_comment_counts)
60
54
  end
61
55
 
62
56
  describe 'instance' do
63
- let(:post) { Post.first }
57
+ let(:post) { Post.first }
64
58
 
65
59
  it "should have a comment_count accessor" do
66
- post.should respond_to(:comments_count)
60
+ post.should respond_to(:comments_count)
67
61
  end
68
62
 
69
63
  it "should be able to get count without preloading them" do
@@ -71,12 +65,12 @@ describe Post do
71
65
  end
72
66
 
73
67
  it "should have an active_comments_count accessor" do
74
- post.should respond_to(:comments_count)
68
+ post.should respond_to(:comments_count)
75
69
  end
76
70
  end
77
71
 
78
72
  describe 'instance with preloaded count' do
79
- let(:post) { Post.preload_comment_counts.first }
73
+ let(:post) { Post.preload_comment_counts.first }
80
74
 
81
75
  it "should be able to get the association count" do
82
76
  post.comments_count.should equal(10)
@@ -90,7 +84,7 @@ end
90
84
 
91
85
  describe PostWithActiveComments do
92
86
  describe 'instance with preloaded count' do
93
- let(:post) { PostWithActiveComments.preload_comment_counts.first }
87
+ let(:post) { PostWithActiveComments.preload_comment_counts.first }
94
88
 
95
89
  it "should be able to get the association count" do
96
90
  post.comments_count.should equal(5)
@@ -1,9 +1,9 @@
1
1
  require 'rspec'
2
+ require 'pry'
3
+ require 'awesome_print'
2
4
  require 'active_record'
3
5
  require 'preload_counts'
4
6
 
5
7
  RSpec.configure do |config|
6
- config.color_enabled = true
7
- config.formatter = 'documentation'
8
8
  end
9
9
 
metadata CHANGED
@@ -1,55 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: preload_counts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Mathieu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-28 00:00:00.000000000 Z
11
+ date: 2015-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
19
+ version: 3.2.0
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: 3.2.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: activerecord
28
+ name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 3.2.0
33
+ version: '2.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 3.2.0
40
+ version: '2.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sqlite3
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description: Preload association or scope counts. This can greatly reduce the number
@@ -60,10 +60,12 @@ executables: []
60
60
  extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
- - .gitignore
64
- - .rvmrc
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".rvmrc"
66
+ - ".travis.yml"
65
67
  - Gemfile
66
- - README.rdoc
68
+ - README.md
67
69
  - Rakefile
68
70
  - lib/preload_counts.rb
69
71
  - lib/preload_counts/ar.rb
@@ -80,17 +82,17 @@ require_paths:
80
82
  - lib
81
83
  required_ruby_version: !ruby/object:Gem::Requirement
82
84
  requirements:
83
- - - '>='
85
+ - - ">="
84
86
  - !ruby/object:Gem::Version
85
87
  version: '0'
86
88
  required_rubygems_version: !ruby/object:Gem::Requirement
87
89
  requirements:
88
- - - '>='
90
+ - - ">="
89
91
  - !ruby/object:Gem::Version
90
92
  version: '0'
91
93
  requirements: []
92
94
  rubyforge_project: preload_counts
93
- rubygems_version: 2.0.3
95
+ rubygems_version: 2.4.5
94
96
  signing_key:
95
97
  specification_version: 4
96
98
  summary: Preload association or scope counts.
@@ -1,82 +0,0 @@
1
- = Preload Counts
2
-
3
- Did you ever write an index page that only needs the count of an association? Consider the following example:
4
-
5
- # Models
6
- class Post < ActiveRecord::Base
7
- has_many :comments
8
- end
9
-
10
- class Comment < ActiveRecord::Base
11
- belongs_to :post
12
-
13
- named_scope :by_moderators, lambda { {:conditions => {:by_moderator => true} }
14
- end
15
-
16
- # Controller
17
- def index
18
- @posts = Post.all
19
- end
20
-
21
- # View
22
- <% @posts.each do |post| %>
23
- <%= post.name %>
24
- (<%= post.comments.count %>)
25
- (<%= post.comments.by_moderators.count %>)
26
- <% end %>
27
-
28
-
29
- This will create two count request to the database for each post each time the view
30
- is rendered. This is really slow. Preload counts helps you minimize the number of
31
- calls to the database with minimal code change.
32
-
33
- == Usage
34
-
35
- # Model
36
- class Post < ActiveRecord::Base
37
- # create a named_scope to preload the comments count as well as the comments
38
- # by_moderators.
39
- preload_counts :comments => [:by_moderators]
40
-
41
- # preload_counts :comments would have only, preloaded the comments which
42
- # would have been slightly faster. You can preload any number of scopes, but
43
- # the more to preload, the more complex and slow the request will be.
44
- has_many :comments
45
- end
46
-
47
- # Controller
48
- def index
49
- # Tell AR to preload the counts in the select request. If you don't specify
50
- # this, the behaviour is to fallback to the slow count.
51
- @posts = Post.preload_comment_counts
52
- end
53
-
54
- # View
55
- # You have different accessor to get to the count. Beware of methods like
56
- # .empty? that has an internal call to count.
57
- <% @posts.each do |post| %>
58
- <%= post.name %>
59
- (<%= post.comments_count %>)
60
- (<%= post.by_moderators_comments_count %>)
61
- <% end %>
62
-
63
- == Benchmark
64
-
65
- More in depth benchmarking is needed, but here's sone annecdotal evidence.
66
-
67
- Without: 650ms per request
68
- With: 450ms per request
69
-
70
- That's right, this saved me 200ms per request with very minimal code change.
71
-
72
- == Support
73
-
74
- This has been tested on Rails 2.3.12 and Rails 3.1.0
75
-
76
- == Help
77
-
78
- - My request is still slow?
79
-
80
- It's possible that this isn't the fix you need. Sometime multiple small requests might be faster than one large request. Always benchmark your page before using this gem.
81
-
82
- Another think you might want to look into is adding index to speedup the query that is being generated by preload_counts.