flyerhzm-bullet 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +57 -41
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/bullet.gemspec +7 -7
- data/lib/bullet.rb +8 -1
- data/lib/bullet/active_record.rb +66 -0
- data/lib/bullet/association.rb +112 -44
- data/lib/bulletware.rb +1 -1
- data/rails/init.rb +0 -3
- data/spec/bullet_association_spec.rb +37 -4
- data/spec/spec_helper.rb +4 -2
- metadata +8 -7
- data/lib/hack/active_record.rb +0 -95
data/README.textile
CHANGED
@@ -1,57 +1,59 @@
|
|
1
1
|
h1. Bullet
|
2
2
|
|
3
|
-
|
3
|
+
The Bullet plugin is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries) or when you're using eager loading that isn't necessary.
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
The others are todo, next may be couter cache.
|
5
|
+
Best practice is to use Bullet in development mode or custom mode (staging, profile, etc.). The last thing you want is your clients getting alerts about how lazy you are.
|
8
6
|
|
9
7
|
****************************************************************************
|
10
8
|
|
11
|
-
h2.
|
9
|
+
h2. Thanks
|
12
10
|
|
13
|
-
|
14
|
-
<pre><code>
|
15
|
-
gem sources -a http://gems.github.com
|
16
|
-
gem install flyerhzm-bullet
|
17
|
-
</code></pre>
|
18
|
-
|
19
|
-
install as plugin:
|
20
|
-
<pre><code>script/plugin install git://github.com/flyerhzm/bullet.git</code></pre>
|
11
|
+
flipsasser added Growl, console.log and Rails.log support, very awesome. And he improved README.
|
21
12
|
|
22
13
|
****************************************************************************
|
23
14
|
|
24
|
-
h2.
|
25
|
-
|
26
|
-
* "Eager Loading, protect N+1 query and protect from unused eager loading":#EagerLoading
|
27
|
-
|
28
|
-
****************************************************************************
|
15
|
+
h2. Install
|
29
16
|
|
30
|
-
|
17
|
+
You can add Bullet to your Rails gem requirements:
|
18
|
+
<pre><code>config.gem 'flyerhzm-bullet', :lib => 'bullet', :source => 'http://gems.github.com'</code></pre>
|
31
19
|
|
32
|
-
|
20
|
+
You can install it as a gem:
|
21
|
+
<pre><code>sudo gem install flyerhzm-bullet --source http://gems.github.com</code></pre>
|
33
22
|
|
23
|
+
Or you can install it as a rails plugin:
|
24
|
+
<pre><code>script/plugin install git://github.com/flyerhzm/bullet.git</code></pre>
|
34
25
|
|
35
26
|
****************************************************************************
|
36
27
|
|
37
|
-
|
38
|
-
<a id="EagerLoading"/></a>
|
39
|
-
|
40
|
-
*important*: It is strongly recommended to disable cache in browser.
|
28
|
+
h2. Configuration
|
41
29
|
|
42
|
-
|
30
|
+
Bullet won't do ANYTHING unless you tell it to explicitly. Append to <code>config/environments/development.rb</code> initializer with the following code:
|
43
31
|
<pre><code>
|
44
|
-
|
45
|
-
Bullet
|
46
|
-
Bullet::Association.alert = true
|
32
|
+
config.after_initialize do
|
33
|
+
Bullet.enable = true
|
34
|
+
Bullet::Association.alert = true
|
35
|
+
Bullet::Association.bullet_logger = true
|
36
|
+
Bullet::Association.console = true
|
37
|
+
Bullet::Association.growl = true
|
38
|
+
Bullet::Association.rails_logger = true
|
39
|
+
end
|
47
40
|
</code></pre>
|
48
|
-
** Bullet.enable (required), if enable is true (default is false), Bullet plugin is enabled. Otherwise, Bullet plugin is disabled.
|
49
|
-
** Bullet::Association.logger (optional), if logger is true (default is true), the N+1 query hints will be appended to <code>log/bullet.log</code> with N+1 query method call stack. Otherwise, no hint to log/bullet.log.
|
50
|
-
** Bullet::Association.alert (optional), if alert is true (default value), alert box will popup if there is N+1 query when browsing web page. Otherwise, no alert box.
|
51
41
|
|
52
|
-
|
42
|
+
The code above will enable all five of the Bullet notification systems:
|
43
|
+
* <code>Bullet.enable</code>: enable Bullet plugin, otherwise do nothing
|
44
|
+
* <code>Bullet::Association.alert</code>: pop up a JavaScript alert in the browser
|
45
|
+
* <code>Bullet::Association.bullet_logger</code>: log to the Bullet log file (RAILS_ROOT/log/bullet.log)
|
46
|
+
* <code>Bullet::Association.rails_logger</code>: add warnings directly to the Rails log
|
47
|
+
* <code>Bullet::Association.console</code>: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
|
48
|
+
* <code>Bullet::Association.growl</code>: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
|
49
|
+
|
50
|
+
****************************************************************************
|
51
|
+
|
52
|
+
h2. Log
|
53
53
|
|
54
|
-
|
54
|
+
The Bullet log <code>log/bullet.log</code> will look something like this:
|
55
|
+
|
56
|
+
* N+1 Query:
|
55
57
|
<pre><code>
|
56
58
|
2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts; model: Post => associations: [comments]·
|
57
59
|
Add to your finder: :include => [:comments]
|
@@ -61,22 +63,36 @@ Add to your finder: :include => [:comments]
|
|
61
63
|
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
|
62
64
|
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
|
63
65
|
</code></pre>
|
64
|
-
|
66
|
+
|
67
|
+
The first two lines are notifications that N+1 queries have been encountered. The remaining lines are stack traces so you can find exactly where the queries were invoked in your code, and fix them.
|
68
|
+
|
69
|
+
* Unsed eager loading:
|
65
70
|
<pre><code>
|
66
|
-
2009-08-25 20:53:56[INFO] Unused
|
71
|
+
2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts; model: Post => associations: [comments]·
|
67
72
|
Remove from your finder: :include => [:comments]
|
68
73
|
</code></pre>
|
69
|
-
It represents that in request '/posts', there is a used eager loading from Post to comments. It means you may have a logic code in controller <code>@posts = Post.find(:all, :include => :comments)</code> should be changed to <code>@posts = Post.find(:all)</code>
|
70
74
|
|
71
|
-
|
75
|
+
These two lines are notifications that unused eager loadings have been encountered.
|
72
76
|
|
73
|
-
|
74
|
-
|
77
|
+
****************************************************************************
|
78
|
+
|
79
|
+
h2. Growl Support
|
80
|
+
|
81
|
+
To get Growl support up-and-running for Bullet, follow the steps below:
|
82
|
+
* Install the ruby-growl gem: <code>sudo gem install ruby-growl</code>
|
83
|
+
* Open the Growl preference pane in Systems Preferences
|
84
|
+
* Click the "Network" tab
|
85
|
+
* Make sure both "Listen for incoming notifications" and "Allow remote application registration" are checked. *Note*: If you set a password, you will need to set <code>Bullet::Association.growl_password = 'your_growl_password</code>' in the config file.
|
86
|
+
* Restart Growl ("General" tab -> Stop Growl -> Start Growl)
|
87
|
+
* Boot up your application. Bullet will automatically send a Growl notification when Growl is turned on. If you do not see it when your application loads, make sure it is enabled in your initializer and double-check the steps above.
|
75
88
|
|
76
89
|
****************************************************************************
|
77
90
|
|
78
|
-
|
79
|
-
|
91
|
+
h2. Step by step example
|
92
|
+
|
93
|
+
Bullet is designed to function as you browse through your application in development. It will alert you whenever it encounters N+1 queries or unused eager loading.
|
94
|
+
|
95
|
+
*Important*: It is strongly recommended you disable your browser's cache.
|
80
96
|
|
81
97
|
1. setup test environment
|
82
98
|
|
data/Rakefile
CHANGED
@@ -23,10 +23,10 @@ end
|
|
23
23
|
Jeweler::Tasks.new do |gemspec|
|
24
24
|
gemspec.name = "bullet"
|
25
25
|
gemspec.summary = "A plugin to kill N+1 queries and unused eager loading"
|
26
|
-
gemspec.description = "
|
26
|
+
gemspec.description = "The Bullet plugin is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries) or when you're using eager loading that isn't necessary."
|
27
27
|
gemspec.email = "flyerhzm@gmail.com"
|
28
|
-
gemspec.homepage = "http://
|
28
|
+
gemspec.homepage = "http://github.com/flyerhzm/bullet"
|
29
29
|
gemspec.authors = ["Richard Huang"]
|
30
30
|
gemspec.files.exclude '.gitignore'
|
31
|
-
gemspec.files.exclude 'log
|
31
|
+
gemspec.files.exclude 'log/*'
|
32
32
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/bullet.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{bullet}
|
8
|
-
s.version = "1.
|
8
|
+
s.version = "1.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Richard Huang"]
|
12
|
-
s.date = %q{2009-08-
|
13
|
-
s.description = %q{
|
12
|
+
s.date = %q{2009-08-29}
|
13
|
+
s.description = %q{The Bullet plugin is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries) or when you're using eager loading that isn't necessary.}
|
14
14
|
s.email = %q{flyerhzm@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"README.textile"
|
@@ -22,24 +22,24 @@ Gem::Specification.new do |s|
|
|
22
22
|
"VERSION",
|
23
23
|
"bullet.gemspec",
|
24
24
|
"lib/bullet.rb",
|
25
|
+
"lib/bullet/active_record.rb",
|
25
26
|
"lib/bullet/association.rb",
|
26
27
|
"lib/bullet/logger.rb",
|
27
28
|
"lib/bulletware.rb",
|
28
|
-
"lib/hack/active_record.rb",
|
29
29
|
"rails/init.rb",
|
30
30
|
"spec/bullet_association_spec.rb",
|
31
31
|
"spec/spec.opts",
|
32
32
|
"spec/spec_helper.rb",
|
33
33
|
"tasks/bullet_tasks.rake"
|
34
34
|
]
|
35
|
-
s.homepage = %q{http://
|
35
|
+
s.homepage = %q{http://github.com/flyerhzm/bullet}
|
36
36
|
s.rdoc_options = ["--charset=UTF-8"]
|
37
37
|
s.require_paths = ["lib"]
|
38
38
|
s.rubygems_version = %q{1.3.5}
|
39
39
|
s.summary = %q{A plugin to kill N+1 queries and unused eager loading}
|
40
40
|
s.test_files = [
|
41
|
-
"spec/
|
42
|
-
"spec/
|
41
|
+
"spec/spec_helper.rb",
|
42
|
+
"spec/bullet_association_spec.rb"
|
43
43
|
]
|
44
44
|
|
45
45
|
if s.respond_to? :specification_version then
|
data/lib/bullet.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
module Bullet
|
2
|
+
@@enable = nil
|
3
|
+
|
2
4
|
class <<self
|
3
5
|
def enable=(enable)
|
4
6
|
@@enable = enable
|
7
|
+
if enable?
|
8
|
+
Bullet::ActiveRecord.enable
|
9
|
+
ActionController::Dispatcher.middleware.use Bulletware
|
10
|
+
end
|
5
11
|
end
|
6
12
|
|
7
13
|
def enable?
|
8
|
-
|
14
|
+
@@enable == true
|
9
15
|
end
|
10
16
|
end
|
11
17
|
|
18
|
+
autoload :ActiveRecord, 'bullet/active_record'
|
12
19
|
autoload :Association, 'bullet/association'
|
13
20
|
autoload :BulletLogger, 'bullet/logger'
|
14
21
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Bullet
|
2
|
+
module ActiveRecord
|
3
|
+
def self.enable
|
4
|
+
::ActiveRecord::Base.class_eval do
|
5
|
+
class << self
|
6
|
+
alias_method :bullet_find_every, :find_every
|
7
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query
|
8
|
+
# if select only one object, then the only one object has impossible to cause N+1 query
|
9
|
+
def find_every(options)
|
10
|
+
records = bullet_find_every(options)
|
11
|
+
|
12
|
+
if records
|
13
|
+
if records.size > 1
|
14
|
+
Bullet::Association.add_possible_objects(records)
|
15
|
+
elsif records.size == 1
|
16
|
+
Bullet::Association.add_impossible_object(records.first)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
records
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
::ActiveRecord::AssociationPreload::ClassMethods.class_eval do
|
26
|
+
alias_method :bullet_preload_associations, :preload_associations
|
27
|
+
# add include for one to many associations query
|
28
|
+
def preload_associations(records, associations, preload_options={})
|
29
|
+
records = [records].flatten.compact.uniq
|
30
|
+
return if records.empty?
|
31
|
+
records.each do |record|
|
32
|
+
Bullet::Association.add_association(record, associations)
|
33
|
+
end
|
34
|
+
bullet_preload_associations(records, associations, preload_options={})
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
::ActiveRecord::Associations::ClassMethods.class_eval do
|
39
|
+
# define one to many associations
|
40
|
+
alias_method :bullet_collection_reader_method, :collection_reader_method
|
41
|
+
def collection_reader_method(reflection, association_proxy_class)
|
42
|
+
Bullet::Association.define_association(self, reflection.name)
|
43
|
+
bullet_collection_reader_method(reflection, association_proxy_class)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
::ActiveRecord::Associations::AssociationCollection.class_eval do
|
48
|
+
# call one to many associations
|
49
|
+
alias_method :bullet_load_target, :load_target
|
50
|
+
def load_target
|
51
|
+
Bullet::Association.call_association(@owner, @reflection.name)
|
52
|
+
bullet_load_target
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
::ActiveRecord::Associations::AssociationProxy.class_eval do
|
57
|
+
# call has_one association
|
58
|
+
alias_method :bullet_load_target, :load_target
|
59
|
+
def load_target
|
60
|
+
Bullet::Association.call_association(@owner, @reflection.name)
|
61
|
+
bullet_load_target
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/bullet/association.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
module Bullet
|
2
|
+
class BulletAssociationError < StandardError
|
3
|
+
end
|
4
|
+
|
2
5
|
class Association
|
3
6
|
class <<self
|
4
|
-
@@
|
5
|
-
@@
|
6
|
-
@@
|
7
|
-
|
7
|
+
@@alert = nil
|
8
|
+
@@bullet_logger = nil
|
9
|
+
@@console = nil
|
10
|
+
@@growl = nil
|
11
|
+
@@growl_password = nil
|
12
|
+
@@rails_logger = nil
|
13
|
+
|
8
14
|
def start_request
|
9
15
|
# puts "start request"
|
10
16
|
end
|
@@ -24,19 +30,42 @@ module Bullet
|
|
24
30
|
@@alert = alert
|
25
31
|
end
|
26
32
|
|
27
|
-
def
|
28
|
-
if
|
29
|
-
@@
|
33
|
+
def bullet_logger=(bullet_logger)
|
34
|
+
if @@bullet_logger = bullet_logger
|
35
|
+
@@logger_file = File.open(Bullet::BulletLogger::LOG_FILE, 'a+')
|
36
|
+
@@logger = Bullet::BulletLogger.new(@@logger_file)
|
30
37
|
end
|
31
38
|
end
|
32
|
-
|
39
|
+
|
40
|
+
def console=(console)
|
41
|
+
@@console = console
|
42
|
+
end
|
43
|
+
|
44
|
+
def growl=(growl)
|
45
|
+
if growl
|
46
|
+
begin
|
47
|
+
require 'ruby-growl'
|
48
|
+
growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password)
|
49
|
+
growl.notify('Bullet Notification', 'Bullet Notification', 'Bullet Growl notifications have been turned on')
|
50
|
+
rescue MissingSourceFile
|
51
|
+
raise BulletAssociationError.new('You must install the ruby-growl gem to use Growl notifications: `sudo gem install ruby-growl`')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@@growl = growl
|
55
|
+
end
|
56
|
+
|
57
|
+
def growl_password=(growl_password)
|
58
|
+
@@growl_password = growl_password
|
59
|
+
end
|
60
|
+
|
61
|
+
def rails_logger=(rails_logger)
|
62
|
+
@@rails_logger = rails_logger
|
63
|
+
end
|
64
|
+
|
33
65
|
def check_unused_preload_associations
|
34
66
|
object_associations.each do |object, association|
|
35
|
-
|
36
|
-
|
37
|
-
call_association.uniq! unless call_association.flatten!.nil?
|
38
|
-
|
39
|
-
add_unused_preload_associations(object.class, association - call_association) unless (association - call_association).empty?
|
67
|
+
call_object_association = call_object_associations[object] || []
|
68
|
+
add_unused_preload_associations(object.class, association - call_object_association) unless (association - call_object_association).empty?
|
40
69
|
end
|
41
70
|
end
|
42
71
|
|
@@ -55,60 +84,90 @@ module Bullet
|
|
55
84
|
|
56
85
|
def bad_associations_alert
|
57
86
|
str = ''
|
87
|
+
if @@alert || @@console || @@growl
|
88
|
+
response = []
|
89
|
+
if has_unused_preload_associations?
|
90
|
+
response.push("Unused eager loadings detected:\n")
|
91
|
+
response.push(*@@unused_preload_associations.to_a.collect{|klazz, associations| klazz_associations_str(klazz, associations)}.join('\n'))
|
92
|
+
end
|
93
|
+
if has_unpreload_associations?
|
94
|
+
response.push("#{"\n" unless response.empty?}N+1 queries detected:\n")
|
95
|
+
response.push(*@@unpreload_associations.to_a.collect{|klazz, associations| " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"}.join('\n'))
|
96
|
+
end
|
97
|
+
end
|
58
98
|
if @@alert
|
59
|
-
str
|
60
|
-
|
61
|
-
|
62
|
-
str << "
|
63
|
-
|
64
|
-
|
65
|
-
|
99
|
+
str << wrap_js_association("alert(#{response.join("\n").inspect});")
|
100
|
+
end
|
101
|
+
if @@console
|
102
|
+
str << wrap_js_association("if (typeof(console) != 'undefined' && console.log) console.log(#{response.join("\n").inspect});")
|
103
|
+
end
|
104
|
+
if @@growl
|
105
|
+
begin
|
106
|
+
growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password)
|
107
|
+
growl.notify('Bullet Notification', 'Bullet Notification', response.join("\n"))
|
108
|
+
rescue
|
109
|
+
end
|
110
|
+
str << '<!-- Sent Growl notification -->'
|
66
111
|
end
|
67
112
|
str
|
68
113
|
end
|
114
|
+
|
115
|
+
def wrap_js_association(message)
|
116
|
+
str = ''
|
117
|
+
str << "<script type=\"text/javascript\">/*<![CDATA[*/"
|
118
|
+
str << message
|
119
|
+
str << "/*]]>*/</script>\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
def log_bad_associations(path)
|
123
|
+
if (@@bullet_logger || @@rails_logger) && (!unpreload_associations.empty? || !unused_preload_associations.empty?)
|
124
|
+
Rails.logger.warn '' if @@rails_logger
|
125
|
+
unused_preload_associations.each do |klazz, associations|
|
126
|
+
log = ["Unused eager loadings: #{path}", klazz_associations_str(klazz, associations), " Remove from your finder: #{associations_str(associations)}"].join("\n")
|
127
|
+
@@logger.info(log) if @@bullet_logger
|
128
|
+
Rails.logger.warn(log) if @@rails_logger
|
129
|
+
end
|
130
|
+
unpreload_associations.each do |klazz, associations|
|
131
|
+
log = ["N+1 Query in #{path}", klazz_associations_str(klazz, associations), " Add to your finder: #{associations_str(associations)}"].join("\n")
|
132
|
+
@@logger.info(log) if @@bullet_logger
|
133
|
+
Rails.logger.warn(log) if @@rails_logger
|
134
|
+
end
|
135
|
+
callers.each do |c|
|
136
|
+
log = ["N+1 Query method call stack", c.map{|line| " #{line}"}].flatten.join("\n")
|
137
|
+
@@logger.info(log) if @@bullet_logger
|
138
|
+
Rails.logger.warn(log) if @@rails_logger
|
139
|
+
end
|
140
|
+
@@logger_file.flush if @@bullet_logger
|
141
|
+
end
|
142
|
+
end
|
69
143
|
|
70
144
|
def bad_associations_str(bad_associations)
|
71
|
-
puts bad_associations.inspect
|
145
|
+
# puts bad_associations.inspect
|
72
146
|
bad_associations.to_a.collect{|klazz, associations| klazz_associations_str(klazz, associations)}.join('\\n')
|
73
147
|
end
|
74
148
|
|
75
149
|
def klazz_associations_str(klazz, associations)
|
76
|
-
"
|
150
|
+
" #{klazz} => [#{associations.map(&:inspect).join(', ')}]"
|
77
151
|
end
|
78
152
|
|
79
153
|
def associations_str(associations)
|
80
154
|
":include => #{associations.map{|a| a.to_sym unless a.is_a? Hash}.inspect}"
|
81
155
|
end
|
82
156
|
|
83
|
-
def log_bad_associations(path)
|
84
|
-
if @@logger
|
85
|
-
unused_preload_associations.each do |klazz, associations|
|
86
|
-
@@logger.info "Unused preload associations: PATH_INFO: #{path}; " + klazz_associations_str(klazz, associations) + "\n Remove from your finder: " + associations_str(associations)
|
87
|
-
end
|
88
|
-
unpreload_associations.each do |klazz, associations|
|
89
|
-
@@logger.info "N+1 Query: PATH_INFO: #{path}; " + klazz_associations_str(klazz, associations) + "\n Add to your finder: " + associations_str(associations)
|
90
|
-
end
|
91
|
-
callers.each do |c|
|
92
|
-
@@logger.info "N+1 Query: method call stack: \n" + c.join("\n")
|
93
|
-
end
|
94
|
-
@@logger_file.flush
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
157
|
def has_klazz_association(klazz)
|
99
158
|
!klazz_associations[klazz].nil? and klazz_associations.keys.include?(klazz)
|
100
159
|
end
|
101
160
|
|
102
161
|
def define_association(klazz, associations)
|
103
|
-
# puts "define association, #{klazz} => #{associations}"
|
162
|
+
# puts "define association, #{klazz} => #{associations.inspect}"
|
104
163
|
add_klazz_associations(klazz, associations)
|
105
164
|
end
|
106
165
|
|
107
166
|
def call_association(object, associations)
|
108
|
-
# puts "call association, #{object} => #{associations}"
|
167
|
+
# puts "call association, #{object} => #{associations.inspect}"
|
168
|
+
add_call_object_associations(object, associations)
|
109
169
|
if unpreload_associations?(object, associations)
|
110
170
|
add_unpreload_associations(object.class, associations)
|
111
|
-
add_call_object_associations(object, associations)
|
112
171
|
caller_in_project
|
113
172
|
end
|
114
173
|
end
|
@@ -124,26 +183,28 @@ module Bullet
|
|
124
183
|
# puts "add unpreload associations, #{klazz} => #{associations.inspect}"
|
125
184
|
unpreload_associations[klazz] ||= []
|
126
185
|
unpreload_associations[klazz] << associations
|
127
|
-
unpreload_associations[klazz]
|
186
|
+
unique(unpreload_associations[klazz])
|
128
187
|
end
|
129
188
|
|
130
189
|
def add_unused_preload_associations(klazz, associations)
|
131
|
-
# puts "add unused preload associations, #{
|
190
|
+
# puts "add unused preload associations, #{klazz} => #{associations.inspect}"
|
132
191
|
unused_preload_associations[klazz] ||= []
|
133
192
|
unused_preload_associations[klazz] << associations
|
134
|
-
unused_preload_associations[klazz]
|
193
|
+
unique(unused_preload_associations[klazz])
|
135
194
|
end
|
136
195
|
|
137
196
|
def add_association(object, associations)
|
138
197
|
# puts "add associations, #{object} => #{associations.inspect}"
|
139
198
|
object_associations[object] ||= []
|
140
199
|
object_associations[object] << associations
|
200
|
+
unique(object_associations[object])
|
141
201
|
end
|
142
202
|
|
143
203
|
def add_call_object_associations(object, associations)
|
144
204
|
# puts "add call object associations, #{object} => #{associations.inspect}"
|
145
205
|
call_object_associations[object] ||= []
|
146
206
|
call_object_associations[object] << associations
|
207
|
+
unique(call_object_associations[object])
|
147
208
|
end
|
148
209
|
|
149
210
|
def add_possible_objects(objects)
|
@@ -151,7 +212,7 @@ module Bullet
|
|
151
212
|
klazz= objects.first.class
|
152
213
|
possible_objects[klazz] ||= []
|
153
214
|
possible_objects[klazz] << objects
|
154
|
-
possible_objects[klazz]
|
215
|
+
unique(possible_objects[klazz])
|
155
216
|
end
|
156
217
|
|
157
218
|
def add_impossible_object(object)
|
@@ -159,12 +220,19 @@ module Bullet
|
|
159
220
|
klazz = object.class
|
160
221
|
impossible_objects[klazz] ||= []
|
161
222
|
impossible_objects[klazz] << object
|
223
|
+
impossible_objects[klazz].uniq!
|
162
224
|
end
|
163
225
|
|
164
226
|
def add_klazz_associations(klazz, associations)
|
165
227
|
# puts "define associations, #{klazz} => #{associations.inspect}"
|
166
228
|
klazz_associations[klazz] ||= []
|
167
229
|
klazz_associations[klazz] << associations
|
230
|
+
unique(klazz_associations[klazz])
|
231
|
+
end
|
232
|
+
|
233
|
+
def unique(array)
|
234
|
+
array.flatten!
|
235
|
+
array.uniq!
|
168
236
|
end
|
169
237
|
|
170
238
|
def unpreload_associations
|
data/lib/bulletware.rb
CHANGED
@@ -12,7 +12,7 @@ class Bulletware
|
|
12
12
|
|
13
13
|
if Bullet::Association.has_bad_assocations?
|
14
14
|
if !headers['Content-Type'].nil? and headers['Content-Type'].include? 'text/html'
|
15
|
-
response_body = response.body
|
15
|
+
response_body = response.body << Bullet::Association.bad_associations_alert
|
16
16
|
headers['Content-Length'] = response_body.length.to_s
|
17
17
|
end
|
18
18
|
|
data/rails/init.rb
CHANGED
@@ -95,17 +95,34 @@ describe Bullet::Association, 'has_many' do
|
|
95
95
|
Bullet::Association.should be_has_unpreload_associations
|
96
96
|
end
|
97
97
|
|
98
|
-
it "should detect unused preload post => comments" do
|
98
|
+
it "should detect unused preload post => comments for post" do
|
99
99
|
Post.find(:all, :include => :comments).collect(&:name)
|
100
100
|
Bullet::Association.check_unused_preload_associations
|
101
101
|
Bullet::Association.should be_has_unused_preload_associations
|
102
102
|
end
|
103
103
|
|
104
|
-
it "should no
|
104
|
+
it "should detect no unused preload post => comments for post" do
|
105
105
|
Post.find(:all).collect(&:name)
|
106
106
|
Bullet::Association.check_unused_preload_associations
|
107
107
|
Bullet::Association.should_not be_has_unused_preload_associations
|
108
108
|
end
|
109
|
+
|
110
|
+
it "should detect no unused preload post => comments for comment" do
|
111
|
+
Post.find(:all).each do |post|
|
112
|
+
post.comments.collect(&:name)
|
113
|
+
end
|
114
|
+
Bullet::Association.check_unused_preload_associations
|
115
|
+
Bullet::Association.should_not be_has_unused_preload_associations
|
116
|
+
|
117
|
+
Bullet::Association.end_request
|
118
|
+
Bullet::Association.start_request
|
119
|
+
|
120
|
+
Post.find(:all).each do |post|
|
121
|
+
post.comments.collect(&:name)
|
122
|
+
end
|
123
|
+
Bullet::Association.check_unused_preload_associations
|
124
|
+
Bullet::Association.should_not be_has_unused_preload_associations
|
125
|
+
end
|
109
126
|
end
|
110
127
|
|
111
128
|
context "category => posts => comments" do
|
@@ -224,7 +241,7 @@ describe Bullet::Association, 'has_many' do
|
|
224
241
|
end
|
225
242
|
Bullet::Association.should be_has_unpreload_associations
|
226
243
|
end
|
227
|
-
|
244
|
+
|
228
245
|
it "should no preload comment => post" do
|
229
246
|
Comment.first.post.name
|
230
247
|
Bullet::Association.should_not be_has_unpreload_associations
|
@@ -236,7 +253,7 @@ describe Bullet::Association, 'has_many' do
|
|
236
253
|
end
|
237
254
|
Bullet::Association.should_not be_has_unpreload_associations
|
238
255
|
end
|
239
|
-
|
256
|
+
|
240
257
|
it "should detect no unused preload comments => post" do
|
241
258
|
Comment.find(:all).collect(&:name)
|
242
259
|
Bullet::Association.check_unused_preload_associations
|
@@ -248,6 +265,22 @@ describe Bullet::Association, 'has_many' do
|
|
248
265
|
Bullet::Association.check_unused_preload_associations
|
249
266
|
Bullet::Association.should be_has_unused_preload_associations
|
250
267
|
end
|
268
|
+
|
269
|
+
it "should dectect no unused preload comments => post" do
|
270
|
+
Comment.find(:all).each do |comment|
|
271
|
+
comment.post.name
|
272
|
+
end
|
273
|
+
Bullet::Association.check_unused_preload_associations
|
274
|
+
Bullet::Association.should_not be_has_unused_preload_associations
|
275
|
+
end
|
276
|
+
|
277
|
+
it "should dectect no unused preload comments => post" do
|
278
|
+
Comment.find(:all, :include => :post).each do |comment|
|
279
|
+
comment.post.name
|
280
|
+
end
|
281
|
+
Bullet::Association.check_unused_preload_associations
|
282
|
+
Bullet::Association.should_not be_has_unused_preload_associations
|
283
|
+
end
|
251
284
|
end
|
252
285
|
end
|
253
286
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'spec/autorun'
|
3
3
|
require 'active_record'
|
4
|
+
require 'action_controller'
|
4
5
|
|
5
|
-
RAILS_ROOT = File.expand_path(__FILE__).split('/')[0..-3].join('/')
|
6
|
+
RAILS_ROOT = File.expand_path(__FILE__).split('/')[0..-3].join('/') unless defined? RAILS_ROOT
|
6
7
|
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/logger'))
|
8
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/active_record'))
|
7
9
|
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/association'))
|
8
10
|
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet'))
|
11
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bulletware'))
|
9
12
|
Bullet.enable = true
|
10
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../lib/hack/active_record'))
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flyerhzm-bullet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
@@ -9,11 +9,11 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-08-
|
12
|
+
date: 2009-08-29 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description:
|
16
|
+
description: The Bullet plugin is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries) or when you're using eager loading that isn't necessary.
|
17
17
|
email: flyerhzm@gmail.com
|
18
18
|
executables: []
|
19
19
|
|
@@ -28,17 +28,18 @@ files:
|
|
28
28
|
- VERSION
|
29
29
|
- bullet.gemspec
|
30
30
|
- lib/bullet.rb
|
31
|
+
- lib/bullet/active_record.rb
|
31
32
|
- lib/bullet/association.rb
|
32
33
|
- lib/bullet/logger.rb
|
33
34
|
- lib/bulletware.rb
|
34
|
-
- lib/hack/active_record.rb
|
35
35
|
- rails/init.rb
|
36
36
|
- spec/bullet_association_spec.rb
|
37
37
|
- spec/spec.opts
|
38
38
|
- spec/spec_helper.rb
|
39
39
|
- tasks/bullet_tasks.rake
|
40
40
|
has_rdoc: false
|
41
|
-
homepage: http://
|
41
|
+
homepage: http://github.com/flyerhzm/bullet
|
42
|
+
licenses:
|
42
43
|
post_install_message:
|
43
44
|
rdoc_options:
|
44
45
|
- --charset=UTF-8
|
@@ -59,10 +60,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
60
|
requirements: []
|
60
61
|
|
61
62
|
rubyforge_project:
|
62
|
-
rubygems_version: 1.
|
63
|
+
rubygems_version: 1.3.5
|
63
64
|
signing_key:
|
64
65
|
specification_version: 3
|
65
66
|
summary: A plugin to kill N+1 queries and unused eager loading
|
66
67
|
test_files:
|
67
|
-
- spec/bullet_association_spec.rb
|
68
68
|
- spec/spec_helper.rb
|
69
|
+
- spec/bullet_association_spec.rb
|
data/lib/hack/active_record.rb
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
if Bullet.enable?
|
2
|
-
ActiveRecord::ActiveRecordError # An ActiveRecord bug
|
3
|
-
|
4
|
-
module ActiveRecord
|
5
|
-
class Base
|
6
|
-
class <<self
|
7
|
-
# if select a collection of objects, then these objects have possible to cause N+1 query
|
8
|
-
# if select only one object, then the only one object has impossible to cause N+1 query
|
9
|
-
alias_method :origin_find_every, :find_every
|
10
|
-
|
11
|
-
def find_every(options)
|
12
|
-
records = origin_find_every(options)
|
13
|
-
|
14
|
-
if records
|
15
|
-
if records.size > 1
|
16
|
-
Bullet::Association.add_possible_objects(records)
|
17
|
-
elsif records.size == 1
|
18
|
-
Bullet::Association.add_impossible_object(records.first)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
records
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
module AssociationPreload
|
28
|
-
module ClassMethods
|
29
|
-
# add include for one to many associations query
|
30
|
-
alias_method :origin_preload_associations, :preload_associations
|
31
|
-
|
32
|
-
def preload_associations(records, associations, preload_options={})
|
33
|
-
records = [records].flatten.compact.uniq
|
34
|
-
return if records.empty?
|
35
|
-
records.each do |record|
|
36
|
-
Bullet::Association.add_association(record, associations)
|
37
|
-
end
|
38
|
-
origin_preload_associations(records, associations, preload_options={})
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
module Associations
|
44
|
-
module ClassMethods
|
45
|
-
# define one to many associations
|
46
|
-
alias_method :origin_collection_reader_method, :collection_reader_method
|
47
|
-
|
48
|
-
def collection_reader_method(reflection, association_proxy_class)
|
49
|
-
Bullet::Association.define_association(self, reflection.name)
|
50
|
-
origin_collection_reader_method(reflection, association_proxy_class)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
class AssociationCollection
|
55
|
-
# call one to many associations
|
56
|
-
alias_method :origin_load_target, :load_target
|
57
|
-
|
58
|
-
def load_target
|
59
|
-
Bullet::Association.call_association(@owner, @reflection.name)
|
60
|
-
origin_load_target
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
class HasOneAssociation
|
65
|
-
# call has_one association
|
66
|
-
alias_method :origin_find_target, :find_target
|
67
|
-
|
68
|
-
def find_target
|
69
|
-
Bullet::Association.call_association(@owner, @reflection.name)
|
70
|
-
origin_find_target
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class BelongsToAssociation
|
75
|
-
# call belongs_to association
|
76
|
-
alias_method :origin_find_target, :find_target
|
77
|
-
|
78
|
-
def find_target
|
79
|
-
Bullet::Association.call_association(@owner, @reflection.name)
|
80
|
-
origin_find_target
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
class BelongsToPolymorphicAssociation
|
85
|
-
# call belongs_to association
|
86
|
-
alias_method :origin_find_target, :find_target
|
87
|
-
|
88
|
-
def find_target
|
89
|
-
Bullet::Association.call_association(@owner, @reflection.name)
|
90
|
-
origin_find_target
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|