preload_counts 0.0.3 → 0.0.4

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
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.