bullet 2.0.0 → 2.1.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.
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rvmrc +2 -0
- data/.rvmrc.example +2 -0
- data/.watchr +24 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +105 -0
- data/Hacking.textile +69 -0
- data/README.textile +37 -55
- data/README_for_rails2.textile +35 -52
- data/Rakefile +45 -0
- data/bullet.gemspec +24 -0
- data/lib/bullet/action_controller2.rb +5 -5
- data/lib/bullet/active_record2.rb +2 -1
- data/lib/bullet/active_record3.rb +2 -1
- data/lib/bullet/active_record31.rb +96 -0
- data/lib/bullet/detector/association.rb +18 -9
- data/lib/bullet/detector/base.rb +0 -7
- data/lib/bullet/detector/unused_eager_association.rb +7 -5
- data/lib/bullet/notification/base.rb +6 -2
- data/lib/bullet/rack.rb +1 -1
- data/lib/bullet/registry/association.rb +1 -2
- data/lib/bullet/registry/base.rb +6 -8
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +6 -5
- data/perf/benchmark.rb +106 -0
- data/rails/init.rb +1 -0
- data/spec/bullet/association_for_chris_spec.rb +96 -0
- data/spec/bullet/association_for_peschkaj_spec.rb +86 -0
- data/spec/bullet/association_spec.rb +889 -0
- data/spec/bullet/counter_spec.rb +136 -0
- data/spec/spec_helper.rb +79 -0
- data/tasks/bullet_tasks.rake +9 -0
- metadata +59 -62
data/bullet.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
|
3
|
+
|
|
4
|
+
require "bullet/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = "bullet"
|
|
8
|
+
s.version = Bullet::VERSION
|
|
9
|
+
s.platform = Gem::Platform::RUBY
|
|
10
|
+
s.authors = ["Richard Huang"]
|
|
11
|
+
s.email = ["flyerhzm@gmail.com"]
|
|
12
|
+
s.homepage = "http://github.com/flyerhzm/bullet"
|
|
13
|
+
s.summary = "A rails plugin to kill N+1 queries and unused eager loading."
|
|
14
|
+
s.description = "A rails plugin to kill N+1 queries and unused eager loading."
|
|
15
|
+
|
|
16
|
+
s.required_rubygems_version = ">= 1.3.6"
|
|
17
|
+
|
|
18
|
+
s.add_dependency "uniform_notifier", "~> 1.0.0"
|
|
19
|
+
|
|
20
|
+
s.files = `git ls-files`.split("\n")
|
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
22
|
+
s.require_paths = ["lib"]
|
|
23
|
+
end
|
|
24
|
+
|
|
@@ -3,7 +3,7 @@ module Bullet
|
|
|
3
3
|
def self.enable
|
|
4
4
|
require 'action_controller'
|
|
5
5
|
case Rails.version
|
|
6
|
-
when /^2.3/
|
|
6
|
+
when /^2.3/
|
|
7
7
|
::ActionController::Dispatcher.middleware.use Bullet::Rack
|
|
8
8
|
::ActionController::Dispatcher.class_eval do
|
|
9
9
|
class <<self
|
|
@@ -22,20 +22,20 @@ module Bullet
|
|
|
22
22
|
Bullet.clear
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
::ActionController::Base.class_eval do
|
|
27
27
|
alias_method :origin_process, :process
|
|
28
28
|
def process(request, response, method = :perform_action, *arguments)
|
|
29
29
|
Bullet.start_request
|
|
30
30
|
response = origin_process(request, response, method = :perform_action, *arguments)
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
if Bullet.notification?
|
|
33
33
|
if response.headers["type"] and response.headers["type"].include? 'text/html' and response.body =~ %r{<html.*</html>}m
|
|
34
34
|
response.body <<= Bullet.gather_inline_notifications
|
|
35
35
|
response.headers["Content-Length"] = response.body.length.to_s
|
|
36
36
|
end
|
|
37
|
-
|
|
38
|
-
Bullet.
|
|
37
|
+
|
|
38
|
+
Bullet.perform_out_of_channel_notifications
|
|
39
39
|
end
|
|
40
40
|
Bullet.end_request
|
|
41
41
|
response
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Bullet
|
|
2
|
+
LOAD_TARGET_RGX = /load_target/
|
|
2
3
|
module ActiveRecord
|
|
3
4
|
def self.enable
|
|
4
5
|
require 'active_record'
|
|
@@ -81,7 +82,7 @@ module Bullet
|
|
|
81
82
|
def load_target
|
|
82
83
|
# avoid stack level too deep
|
|
83
84
|
result = origin_load_target
|
|
84
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.
|
|
85
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.find {|c| c =~ LOAD_TARGET_RGX }
|
|
85
86
|
Bullet::Detector::Association.add_possible_objects(result)
|
|
86
87
|
result
|
|
87
88
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Bullet
|
|
2
|
+
LOAD_TARGET_RGX = /load_target/
|
|
2
3
|
module ActiveRecord
|
|
3
4
|
def self.enable
|
|
4
5
|
require 'active_record'
|
|
@@ -75,7 +76,7 @@ module Bullet
|
|
|
75
76
|
def load_target
|
|
76
77
|
# avoid stack level too deep
|
|
77
78
|
result = origin_load_target
|
|
78
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.
|
|
79
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.find {|c| c =~ LOAD_TARGET_RGX }
|
|
79
80
|
Bullet::Detector::Association.add_possible_objects(result)
|
|
80
81
|
result
|
|
81
82
|
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
LOAD_TARGET_RGX = /load_target/
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
def self.enable
|
|
5
|
+
require 'active_record'
|
|
6
|
+
::ActiveRecord::Relation.class_eval do
|
|
7
|
+
alias_method :origin_to_a, :to_a
|
|
8
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
9
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
10
|
+
def to_a
|
|
11
|
+
records = origin_to_a
|
|
12
|
+
if records.size > 1
|
|
13
|
+
Bullet::Detector::Association.add_possible_objects(records)
|
|
14
|
+
Bullet::Detector::Counter.add_possible_objects(records)
|
|
15
|
+
elsif records.size == 1
|
|
16
|
+
Bullet::Detector::Association.add_impossible_object(records.first)
|
|
17
|
+
Bullet::Detector::Counter.add_impossible_object(records.first)
|
|
18
|
+
end
|
|
19
|
+
records
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
::ActiveRecord::Associations::Preloader.class_eval do
|
|
24
|
+
# include query for one to many associations.
|
|
25
|
+
# keep this eager loadings.
|
|
26
|
+
alias_method :origin_initialize, :initialize
|
|
27
|
+
def initialize(records, associations, preload_options = {})
|
|
28
|
+
origin_initialize(records, associations, preload_options)
|
|
29
|
+
records = [records].flatten.compact.uniq
|
|
30
|
+
return if records.empty?
|
|
31
|
+
records.each do |record|
|
|
32
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
33
|
+
end
|
|
34
|
+
Bullet::Detector::Association.add_eager_loadings(records, associations)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
::ActiveRecord::FinderMethods.class_eval do
|
|
39
|
+
# add includes in scope
|
|
40
|
+
alias_method :origin_find_with_associations, :find_with_associations
|
|
41
|
+
def find_with_associations
|
|
42
|
+
records = origin_find_with_associations
|
|
43
|
+
associations = (@eager_load_values + @includes_values).uniq
|
|
44
|
+
records.each do |record|
|
|
45
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
46
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
47
|
+
end
|
|
48
|
+
Bullet::Detector::Association.add_eager_loadings(records, associations)
|
|
49
|
+
records
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
::ActiveRecord::Associations::JoinDependency.class_eval do
|
|
54
|
+
alias_method :origin_construct_association, :construct_association
|
|
55
|
+
# call join associations
|
|
56
|
+
def construct_association(record, join, row)
|
|
57
|
+
associations = join.reflection.name
|
|
58
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
59
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
60
|
+
origin_construct_association(record, join, row)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
::ActiveRecord::Associations::CollectionAssociation.class_eval do
|
|
65
|
+
# call one to many associations
|
|
66
|
+
alias_method :origin_load_target, :load_target
|
|
67
|
+
def load_target
|
|
68
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
69
|
+
origin_load_target
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
::ActiveRecord::Associations::Association.class_eval do
|
|
74
|
+
# call has_one and belong_to association
|
|
75
|
+
alias_method :origin_load_target, :load_target
|
|
76
|
+
def load_target
|
|
77
|
+
# avoid stack level too deep
|
|
78
|
+
result = origin_load_target
|
|
79
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.find {|c| c =~ LOAD_TARGET_RGX }
|
|
80
|
+
Bullet::Detector::Association.add_possible_objects(result)
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
|
86
|
+
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
87
|
+
|
|
88
|
+
def has_cached_counter?
|
|
89
|
+
result = origin_has_cached_counter?
|
|
90
|
+
Bullet::Detector::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
91
|
+
result
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -39,25 +39,35 @@ module Bullet
|
|
|
39
39
|
def add_eager_loadings(objects, associations)
|
|
40
40
|
objects = Array(objects)
|
|
41
41
|
|
|
42
|
+
to_add = nil
|
|
43
|
+
to_merge, to_delete = [], []
|
|
42
44
|
eager_loadings.each do |k, v|
|
|
43
45
|
key_objects_overlap = k & objects
|
|
44
46
|
|
|
45
47
|
next if key_objects_overlap.empty?
|
|
46
48
|
|
|
47
49
|
if key_objects_overlap == k
|
|
48
|
-
|
|
50
|
+
to_add = [k, associations]
|
|
49
51
|
break
|
|
50
52
|
|
|
51
53
|
else
|
|
52
|
-
|
|
54
|
+
to_merge << [key_objects_overlap, ( eager_loadings[k].dup << associations )]
|
|
53
55
|
|
|
54
56
|
keys_without_objects = k - objects
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
eager_loadings.delete(k)
|
|
57
|
+
to_merge << [keys_without_objects, eager_loadings[k]]
|
|
58
|
+
to_delete << k
|
|
58
59
|
objects = objects - k
|
|
59
60
|
end
|
|
60
61
|
end
|
|
62
|
+
if to_add
|
|
63
|
+
eager_loadings.add *to_add
|
|
64
|
+
end
|
|
65
|
+
to_merge.each do |k,val|
|
|
66
|
+
eager_loadings.merge k, val
|
|
67
|
+
end
|
|
68
|
+
to_delete.each do |k|
|
|
69
|
+
eager_loadings.delete k
|
|
70
|
+
end
|
|
61
71
|
|
|
62
72
|
eager_loadings.add objects, associations unless objects.empty?
|
|
63
73
|
end
|
|
@@ -73,15 +83,14 @@ module Bullet
|
|
|
73
83
|
|
|
74
84
|
# check if object => associations already exists in object_associations.
|
|
75
85
|
def association?(object, associations)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
value = object_associations[object]
|
|
87
|
+
if value
|
|
79
88
|
value.each do |v|
|
|
80
89
|
result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
|
|
81
90
|
return true if result
|
|
82
91
|
end
|
|
83
|
-
|
|
84
92
|
end
|
|
93
|
+
|
|
85
94
|
return false
|
|
86
95
|
end
|
|
87
96
|
|
data/lib/bullet/detector/base.rb
CHANGED
|
@@ -23,11 +23,13 @@ module Bullet
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def self.call_object_association( object, association )
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
all = Set.new
|
|
27
|
+
eager_loadings.similarly_associated( object, association ).each do |related_object|
|
|
28
|
+
coa = call_object_associations[related_object]
|
|
29
|
+
next if coa.nil?
|
|
30
|
+
all.merge coa
|
|
31
|
+
end
|
|
32
|
+
all.to_a
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def self.diff_object_association( object, association )
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Bullet
|
|
2
2
|
module Notification
|
|
3
3
|
class Base
|
|
4
|
-
attr_accessor :notifier
|
|
4
|
+
attr_accessor :notifier, :url
|
|
5
5
|
attr_reader :base_class, :associations, :path
|
|
6
6
|
|
|
7
7
|
def initialize( base_class, associations, path = nil )
|
|
@@ -15,6 +15,10 @@ module Bullet
|
|
|
15
15
|
|
|
16
16
|
def body
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
def whoami
|
|
20
|
+
"user: " << `whoami`.chomp
|
|
21
|
+
end
|
|
18
22
|
|
|
19
23
|
def body_with_caller
|
|
20
24
|
body
|
|
@@ -25,7 +29,7 @@ module Bullet
|
|
|
25
29
|
end
|
|
26
30
|
|
|
27
31
|
def full_notice
|
|
28
|
-
|
|
32
|
+
[whoami, url, title, body_with_caller].compact.join("\n")
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
def notify_inline
|
data/lib/bullet/rack.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Bullet
|
|
|
16
16
|
response_body = response.body << Bullet.gather_inline_notifications
|
|
17
17
|
headers['Content-Length'] = response_body.length.to_s
|
|
18
18
|
end
|
|
19
|
-
Bullet.perform_out_of_channel_notifications
|
|
19
|
+
Bullet.perform_out_of_channel_notifications(env)
|
|
20
20
|
end
|
|
21
21
|
response_body ||= response.body
|
|
22
22
|
Bullet.end_request
|
|
@@ -3,13 +3,12 @@ module Bullet
|
|
|
3
3
|
class Association < Base
|
|
4
4
|
def merge( base, associations )
|
|
5
5
|
@registry.merge!( { base => associations } )
|
|
6
|
-
unique( @registry[base] )
|
|
7
6
|
end
|
|
8
7
|
|
|
9
8
|
def similarly_associated( base, associations )
|
|
10
9
|
@registry.select do |key, value|
|
|
11
10
|
key.include?( base ) and value == associations
|
|
12
|
-
end.collect( &:first ).flatten
|
|
11
|
+
end.collect( &:first ).flatten
|
|
13
12
|
end
|
|
14
13
|
end
|
|
15
14
|
end
|
data/lib/bullet/registry/base.rb
CHANGED
|
@@ -24,16 +24,14 @@ module Bullet
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def add( key, value )
|
|
27
|
-
@registry[key] ||=
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
@registry[key] ||= Set.new
|
|
28
|
+
if value.is_a? Array
|
|
29
|
+
@registry[key] += value
|
|
30
|
+
else
|
|
31
|
+
@registry[key] << value
|
|
32
|
+
end
|
|
30
33
|
end
|
|
31
34
|
|
|
32
|
-
private
|
|
33
|
-
def unique( array )
|
|
34
|
-
array.flatten!
|
|
35
|
-
array.uniq!
|
|
36
|
-
end
|
|
37
35
|
end
|
|
38
36
|
end
|
|
39
37
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
require 'set'
|
|
2
|
+
require 'uniform_notifier'
|
|
2
3
|
|
|
3
4
|
module Bullet
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if Rails.version =~ /^3.0/
|
|
5
|
+
if Rails.version =~ /^3\.0/
|
|
7
6
|
autoload :ActiveRecord, 'bullet/active_record3'
|
|
7
|
+
elsif Rails.version =~ /^3\.1/
|
|
8
|
+
autoload :ActiveRecord, 'bullet/active_record31'
|
|
8
9
|
else
|
|
9
10
|
autoload :ActiveRecord, 'bullet/active_record2'
|
|
10
11
|
autoload :ActionController, 'bullet/action_controller2'
|
|
@@ -17,7 +18,6 @@ module Bullet
|
|
|
17
18
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
|
18
19
|
|
|
19
20
|
if defined? Rails::Railtie
|
|
20
|
-
# compatible with rails 3.0.0.beta4
|
|
21
21
|
class BulletRailtie < Rails::Railtie
|
|
22
22
|
initializer "bullet.configure_rails_initialization" do |app|
|
|
23
23
|
app.middleware.use Bullet::Rack
|
|
@@ -87,8 +87,9 @@ module Bullet
|
|
|
87
87
|
responses.join( "\n" )
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
def perform_out_of_channel_notifications
|
|
90
|
+
def perform_out_of_channel_notifications(env = {})
|
|
91
91
|
for_each_active_notifier_with_notification do |notification|
|
|
92
|
+
notification.url = [env['HTTP_HOST'], env['REQUEST_URI']].compact.join
|
|
92
93
|
notification.notify_out_of_channel
|
|
93
94
|
end
|
|
94
95
|
end
|
data/perf/benchmark.rb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
$: << 'lib'
|
|
2
|
+
require 'benchmark'
|
|
3
|
+
require 'rails'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require 'activerecord-import'
|
|
6
|
+
require 'bullet'
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
require 'perftools'
|
|
10
|
+
rescue LoadError
|
|
11
|
+
puts "Could not load perftools.rb, profiling won't be possible"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Post < ActiveRecord::Base
|
|
15
|
+
belongs_to :user
|
|
16
|
+
has_many :comments
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Comment < ActiveRecord::Base
|
|
20
|
+
belongs_to :user
|
|
21
|
+
belongs_to :post
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class User < ActiveRecord::Base
|
|
25
|
+
has_many :posts
|
|
26
|
+
has_many :comments
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# create database bullet_benchmark;
|
|
30
|
+
ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'bullet_benchmark', :server => '/tmp/mysql.socket', :username => 'root')
|
|
31
|
+
|
|
32
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
|
33
|
+
ActiveRecord::Base.connection.drop_table(table)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ActiveRecord::Schema.define(:version => 1) do
|
|
37
|
+
create_table :posts do |t|
|
|
38
|
+
t.column :title, :string
|
|
39
|
+
t.column :body, :string
|
|
40
|
+
t.column :user_id, :integer
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
create_table :comments do |t|
|
|
44
|
+
t.column :body, :string
|
|
45
|
+
t.column :post_id, :integer
|
|
46
|
+
t.column :user_id, :integer
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
create_table :users do |t|
|
|
50
|
+
t.column :name, :string
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
users_size = 100
|
|
55
|
+
posts_size = 1000
|
|
56
|
+
comments_size = 10000
|
|
57
|
+
users = []
|
|
58
|
+
users_size.times do |i|
|
|
59
|
+
users << User.new(:name => "user#{i}")
|
|
60
|
+
end
|
|
61
|
+
User.import users
|
|
62
|
+
users = User.all
|
|
63
|
+
|
|
64
|
+
posts = []
|
|
65
|
+
posts_size.times do |i|
|
|
66
|
+
posts << Post.new(:title => "Title #{i}", :body => "Body #{i}", :user => users[i%100])
|
|
67
|
+
end
|
|
68
|
+
Post.import posts
|
|
69
|
+
posts = Post.all
|
|
70
|
+
|
|
71
|
+
comments = []
|
|
72
|
+
comments_size.times do |i|
|
|
73
|
+
comments << Comment.new(:body => "Comment #{i}", :post => posts[i%1000], :user => users[i%100])
|
|
74
|
+
end
|
|
75
|
+
Comment.import comments
|
|
76
|
+
|
|
77
|
+
puts "Start benchmarking..."
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Bullet.enable = true
|
|
81
|
+
|
|
82
|
+
PerfTools::CpuProfiler.start(ARGV[0]|| "benchmark_profile") if defined? PerfTools
|
|
83
|
+
|
|
84
|
+
Benchmark.bm(70) do |bm|
|
|
85
|
+
bm.report("Querying & Iterating #{posts_size} Posts with #{comments_size} Comments and #{users_size} Users") do
|
|
86
|
+
Bullet.start_request
|
|
87
|
+
Post.select("SQL_NO_CACHE *").includes(:user, :comments => :user).each do |p|
|
|
88
|
+
p.title
|
|
89
|
+
p.user.name
|
|
90
|
+
p.comments.each do |c|
|
|
91
|
+
c.body
|
|
92
|
+
c.user.name
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
Bullet.end_request
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
PerfTools::CpuProfiler.stop if defined? PerfTools
|
|
100
|
+
puts "End benchmarking..."
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# 2.0.1
|
|
104
|
+
# user system total real
|
|
105
|
+
# Querying & Iterating 100 Posts with 10000 Comments and 100 Users 2.290000 0.050000 2.340000 ( 2.366174)
|
|
106
|
+
|
data/rails/init.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'bullet'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
|
2
|
+
|
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
|
4
|
+
# This test is just used for http://github.com/flyerhzm/bullet/issues/#issue/14
|
|
5
|
+
describe Bullet::Detector::Association do
|
|
6
|
+
|
|
7
|
+
describe "for chris" do
|
|
8
|
+
def setup_db
|
|
9
|
+
ActiveRecord::Schema.define(:version => 1) do
|
|
10
|
+
create_table :locations do |t|
|
|
11
|
+
t.column :name, :string
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
create_table :hotels do |t|
|
|
15
|
+
t.column :name, :string
|
|
16
|
+
t.column :location_id, :integer
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
create_table :deals do |t|
|
|
20
|
+
t.column :name, :string
|
|
21
|
+
t.column :hotel_id, :integer
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def teardown_db
|
|
27
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
|
28
|
+
ActiveRecord::Base.connection.drop_table(table)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Location < ActiveRecord::Base
|
|
33
|
+
has_many :hotels
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Hotel < ActiveRecord::Base
|
|
37
|
+
belongs_to :location
|
|
38
|
+
has_many :deals
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Deal < ActiveRecord::Base
|
|
42
|
+
belongs_to :hotel
|
|
43
|
+
has_one :location, :through => :hotel
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
before(:all) do
|
|
47
|
+
setup_db
|
|
48
|
+
|
|
49
|
+
location1 = Location.create(:name => "location1")
|
|
50
|
+
location2 = Location.create(:name => "location2")
|
|
51
|
+
|
|
52
|
+
hotel1 = location1.hotels.create(:name => "hotel1")
|
|
53
|
+
hotel2 = location1.hotels.create(:name => "hotel2")
|
|
54
|
+
hotel3 = location2.hotels.create(:name => "hotel3")
|
|
55
|
+
hotel4 = location2.hotels.create(:name => "hotel4")
|
|
56
|
+
|
|
57
|
+
deal1 = hotel1.deals.create(:name => "deal1")
|
|
58
|
+
deal2 = hotel2.deals.create(:name => "deal2")
|
|
59
|
+
deal3 = hotel3.deals.create(:name => "deal3")
|
|
60
|
+
deal4 = hotel4.deals.create(:name => "deal4")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
after(:all) do
|
|
64
|
+
teardown_db
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
before(:each) do
|
|
68
|
+
Bullet.start_request
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
after(:each) do
|
|
72
|
+
Bullet.end_request
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "should detect unpreload association from deal to hotel" do
|
|
76
|
+
Deal.all.each do |deal|
|
|
77
|
+
deal.hotel.location.name
|
|
78
|
+
end
|
|
79
|
+
Bullet::Detector::Association.should be_detecting_unpreloaded_association_for(Deal, :hotel)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "should detect unpreload association from hotel to location" do
|
|
83
|
+
Deal.includes(:hotel).each do |deal|
|
|
84
|
+
deal.hotel.location.name
|
|
85
|
+
end
|
|
86
|
+
Bullet::Detector::Association.should be_detecting_unpreloaded_association_for(Hotel, :location)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "should not detect unpreload association" do
|
|
90
|
+
Deal.includes({:hotel => :location}).each do |deal|
|
|
91
|
+
deal.hotel.location.name
|
|
92
|
+
end
|
|
93
|
+
Bullet::Detector::Association.should_not be_has_unused_preload_associations
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|