bullet 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,381 @@
1
+ h1. Bullet
2
+
3
+ The Bullet plugin/gem 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), when you're using eager loading that isn't necessary and when you should use counter cache.
4
+
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.
6
+
7
+ This plugin/gem uses <code>ActionController::Dispatcher.middleware</code> which is introduce by rails 2.3.
8
+
9
+ ****************************************************************************
10
+
11
+ h2. Change
12
+
13
+ There is a large refactor from gem 1.4 to 1.5, so if you upgrade to 1.5 gem, please read the Configuration section again and rewrite your configuration.
14
+
15
+ ****************************************************************************
16
+
17
+ h2. Thanks
18
+
19
+ flipsasser added Growl, console.log and Rails.log support, very awesome. And he improved README.
20
+ rainux add group style console.log.
21
+ 2collegebums add some specs to generate red bar.
22
+
23
+ ****************************************************************************
24
+
25
+ h2. Install
26
+
27
+ You can add Bullet to your Rails gem requirements:
28
+ <pre><code>config.gem 'flyerhzm-bullet', :lib => 'bullet', :source => 'http://gems.github.com'</code></pre>
29
+
30
+ You can install it as a gem:
31
+ <pre><code>sudo gem install flyerhzm-bullet --source http://gems.github.com</code></pre>
32
+
33
+ Or you can install it as a rails plugin:
34
+ <pre><code>script/plugin install git://github.com/flyerhzm/bullet.git</code></pre>
35
+
36
+ ****************************************************************************
37
+
38
+ h2. Configuration
39
+
40
+ Bullet won't do ANYTHING unless you tell it to explicitly. Append to <code>config/environments/development.rb</code> initializer with the following code:
41
+ <pre><code>
42
+ config.after_initialize do
43
+ Bullet.enable = true
44
+ Bullet.alert = true
45
+ Bullet.bullet_logger = true
46
+ Bullet.console = true
47
+ Bullet.growl = true
48
+ Bullet.rails_logger = true
49
+ Bullet.disable_browser_cache = true
50
+ end
51
+ </code></pre>
52
+
53
+ It is recommended to config growl notification as follows if your collaborators are not using MacOS
54
+ <pre><code>
55
+ begin
56
+ require 'ruby-growl'
57
+ Bullet.growl = true
58
+ rescue MissingSourceFile
59
+ end
60
+ </code></pre>
61
+
62
+ The code above will enable all five of the Bullet notification systems:
63
+ * <code>Bullet.enable</code>: enable Bullet plugin/gem, otherwise do nothing
64
+ * <code>Bullet.alert</code>: pop up a JavaScript alert in the browser
65
+ * <code>Bullet.bullet_logger</code>: log to the Bullet log file (RAILS_ROOT/log/bullet.log)
66
+ * <code>Bullet.rails_logger</code>: add warnings directly to the Rails log
67
+ * <code>Bullet.console</code>: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
68
+ * <code>Bullet.growl</code>: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
69
+ * <code>Bullet.disable_browser_cache</code>: disable browser cache which usually causes unexpected problems
70
+
71
+ ****************************************************************************
72
+
73
+ h2. Log
74
+
75
+ The Bullet log <code>log/bullet.log</code> will look something like this:
76
+
77
+ * N+1 Query:
78
+ <pre><code>
79
+ 2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts; model: Post => associations: [comments]·
80
+ Add to your finder: :include => [:comments]
81
+ 2009-08-25 20:40:17[INFO] N+1 Query: method call stack:·
82
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
83
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
84
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
85
+ /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
86
+ </code></pre>
87
+
88
+ 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.
89
+
90
+ * Unused eager loading:
91
+ <pre><code>
92
+ 2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts; model: Post => associations: [comments]·
93
+ Remove from your finder: :include => [:comments]
94
+ </code></pre>
95
+
96
+ These two lines are notifications that unused eager loadings have been encountered.
97
+
98
+ * Need counter cache:
99
+ <pre><code>
100
+ 2009-09-11 09:46:50[INFO] Need Counter Cache
101
+ Post => [:comments]
102
+ </code></pre>
103
+
104
+ ****************************************************************************
105
+
106
+ h2. Growl Support
107
+
108
+ To get Growl support up-and-running for Bullet, follow the steps below:
109
+ * Install the ruby-growl gem: <code>sudo gem install ruby-growl</code>
110
+ * Open the Growl preference pane in Systems Preferences
111
+ * Click the "Network" tab
112
+ * 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.growl_password = 'your_growl_password</code>' in the config file.
113
+ * Restart Growl ("General" tab -> Stop Growl -> Start Growl)
114
+ * 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.
115
+
116
+ ****************************************************************************
117
+
118
+ h2. Important
119
+
120
+ If you encounter the following errors in development environment:
121
+
122
+ <pre><code>
123
+ You might have expected an instance of Array.
124
+ The error occurred while evaluating nil.include?
125
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:142:in `create_time_zone_conversion_attribute?'
126
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:75:in `define_attribute_methods'
127
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `each'
128
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `define_attribute_methods'
129
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:242:in `method_missing'
130
+ </code></pre>
131
+
132
+ Or any strange behavior of bullet plugin/gem, *please disable your browser's cache*.
133
+
134
+ ****************************************************************************
135
+
136
+ h2. Advance
137
+
138
+ The bullet plugin/gem use rack middleware for http request. If you want to bullet for without http server, such as job server. You can do like this:
139
+
140
+ <pre><code>
141
+ Bullet.start_request if Bullet.enable?
142
+ # run job
143
+ if Bullet.enable?
144
+ Bullet.growl_notification
145
+ Bullet.log_notification('JobServer: ')
146
+ Bullet.end_request
147
+ end
148
+ </code></pre>
149
+
150
+ Or you want to use it in test mode
151
+
152
+ <pre><code>
153
+ before(:each)
154
+ Bullet.start_request if Bullet.enable?
155
+ end
156
+
157
+ after(:each)
158
+ if Bullet.enable?
159
+ Bullet.growl_notification
160
+ Bullet.log_notification('Test: ')
161
+ Bullet.end_request
162
+ end
163
+ end
164
+ </code></pre>
165
+
166
+ ****************************************************************************
167
+
168
+ h2. Step by step example
169
+
170
+ 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.
171
+
172
+ 1. setup test environment
173
+
174
+ <pre><code>
175
+ $ rails test
176
+ $ cd test
177
+ $ script/generate scaffold post name:string
178
+ $ script/generate scaffold comment name:string post_id:integer
179
+ $ rake db:migrate
180
+ </code></pre>
181
+
182
+ 2. change <code>app/model/post.rb</code> and <code>app/model/comment.rb</code>
183
+
184
+ <pre><code>
185
+ class Post < ActiveRecord::Base
186
+ has_many :comments
187
+ end
188
+
189
+ class Comment < ActiveRecord::Base
190
+ belongs_to :post
191
+ end
192
+ </code></pre>
193
+
194
+ 3. go to script/console and execute
195
+
196
+ <pre><code>
197
+ post1 = Post.create(:name => 'first')
198
+ post2 = Post.create(:name => 'second')
199
+ post1.comments.create(:name => 'first')
200
+ post1.comments.create(:name => 'second')
201
+ post2.comments.create(:name => 'third')
202
+ post2.comments.create(:name => 'fourth')
203
+ </code></pre>
204
+
205
+ 4. change the <code>app/views/posts/index.html.erb</code> to produce a N+1 query
206
+
207
+ <pre><code>
208
+ <% @posts.each do |post| %>
209
+ <tr>
210
+ <td><%=h post.name %></td>
211
+ <td><%= post.comments.collect(&:name) %></td>
212
+ <td><%= link_to 'Show', post %></td>
213
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
214
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
215
+ </tr>
216
+ <% end %>
217
+ </code></pre>
218
+
219
+ 5. add bullet plugin
220
+
221
+ <pre><code>
222
+ $ script/plugin install git://github.com/flyerhzm/bullet.git
223
+ </code></pre>
224
+
225
+ 6. enable the bullet plugin in development, add a line to <code>config/environments/development.rb</code>
226
+
227
+ <pre><code>
228
+ config.after_initialize do
229
+ Bullet.enable = true
230
+ Bullet.alert = true
231
+ Bullet.bullet_logger = true
232
+ Bullet.console = true
233
+ # Bullet.growl = true
234
+ Bullet.rails_logger = true
235
+ Bullet.disable_browser_cache = true
236
+ end
237
+ </code></pre>
238
+
239
+ 7. start server
240
+
241
+ <pre><code>
242
+ $ script/server
243
+ </code></pre>
244
+
245
+ 8. input http://localhost:3000/posts in browser, then you will see a popup alert box says
246
+
247
+ <pre><code>
248
+ The request has unused preload associations as follows:
249
+ None
250
+ The request has N+1 queries as follows:
251
+ model: Post => associations: [comment]
252
+ </code></pre>
253
+
254
+ which means there is a N+1 query from post object to comments associations.
255
+
256
+ In the meanwhile, there's a log appended into <code>log/bullet.log</code> file
257
+
258
+ <pre><code>
259
+ 2009-08-20 09:12:19[INFO] N+1 Query: PATH_INFO: /posts; model: Post => assocations: [comments]
260
+ Add your finder: :include => [:comments]
261
+ 2009-08-20 09:12:19[INFO] N+1 Query: method call stack:
262
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
263
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
264
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
265
+ /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
266
+ </code></pre>
267
+
268
+ The generated SQLs are
269
+
270
+ <pre><code>
271
+ Post Load (1.0ms) SELECT * FROM "posts"
272
+ Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments".post_id = 1)
273
+ Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments".post_id = 2)
274
+ </code></pre>
275
+
276
+
277
+ 9. fix the N+1 query, change <code>app/controllers/posts_controller.rb</code> file
278
+
279
+ <pre><code>
280
+ def index
281
+ @posts = Post.find(:all, :include => :comments)
282
+
283
+ respond_to do |format|
284
+ format.html # index.html.erb
285
+ format.xml { render :xml => @posts }
286
+ end
287
+ end
288
+ </code></pre>
289
+
290
+ 10. refresh http://localhost:3000/posts page, no alert box and no log appended.
291
+
292
+ The generated SQLs are
293
+
294
+ <pre><code>
295
+ Post Load (0.5ms) SELECT * FROM "posts"
296
+ Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id IN (1,2))
297
+ </code></pre>
298
+
299
+ a N+1 query fixed. Cool!
300
+
301
+ 11. now simulate unused eager loading. Change <code>app/controllers/posts_controller.rb</code> and <code>app/views/posts/index.html.erb</code>
302
+
303
+ <pre><code>
304
+ def index
305
+ @posts = Post.find(:all, :include => :comments)
306
+
307
+ respond_to do |format|
308
+ format.html # index.html.erb
309
+ format.xml { render :xml => @posts }
310
+ end
311
+ end
312
+ </code></pre>
313
+
314
+ <pre><code>
315
+ <% @posts.each do |post| %>
316
+ <tr>
317
+ <td><%=h post.name %></td>
318
+ <td><%= link_to 'Show', post %></td>
319
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
320
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
321
+ </tr>
322
+ <% end %>
323
+ </code></pre>
324
+
325
+ 12. refresh http://localhost:3000/posts page, then you will see a popup alert box says
326
+
327
+ <pre><code>
328
+ The request has unused preload associations as follows:
329
+ model: Post => associations: [comment]
330
+ The request has N+1 queries as follows:
331
+ None
332
+ </code></pre>
333
+
334
+ In the meanwhile, there's a log appended into <code>log/bullet.log</code> file
335
+
336
+ <pre><code>
337
+ 2009-08-25 21:13:22[INFO] Unused preload associations: PATH_INFO: /posts; model: Post => associations: [comments]·
338
+ Remove from your finder: :include => [:comments]
339
+ </code></pre>
340
+
341
+ 13. simulate counter_cache. Change <code>app/controllers/posts_controller.rb</code> and <code>app/views/posts/index.html.erb</code>
342
+
343
+ <pre><code>
344
+ def index
345
+ @posts = Post.find(:all)
346
+
347
+ respond_to do |format|
348
+ format.html # index.html.erb
349
+ format.xml { render :xml => @posts }
350
+ end
351
+ end
352
+ </code></pre>
353
+
354
+ <pre><code>
355
+ <% @posts.each do |post| %>
356
+ <tr>
357
+ <td><%=h post.name %></td>
358
+ <td><%=h post.comments.size %></td>
359
+ <td><%= link_to 'Show', post %></td>
360
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
361
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
362
+ </tr>
363
+ <% end %>
364
+ </code></pre>
365
+
366
+ 14. refresh http://localhost:3000/posts page, then you will see a popup alert box says
367
+
368
+ <pre><code>
369
+ Need counter cache
370
+ Post => [:comments]
371
+ </code></pre>
372
+
373
+ In the meanwhile, there's a log appended into <code>log/bullet.log</code> file.
374
+
375
+ <pre><code>
376
+ 2009-09-11 10:07:10[INFO] Need Counter Cache
377
+ Post => [:comments]
378
+ </code></pre>
379
+
380
+
381
+ Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com), released under the MIT license
@@ -0,0 +1,33 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+ require 'rake/rdoctask'
4
+ require 'jeweler'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :spec
8
+
9
+ desc 'Generate documentation for the sitemap plugin.'
10
+ Rake::RDocTask.new(:rdoc) do |rdoc|
11
+ rdoc.rdoc_dir = 'rdoc'
12
+ rdoc.title = 'Bullet'
13
+ rdoc.options << '--line-numbers' << '--inline-source'
14
+ rdoc.rdoc_files.include('README')
15
+ rdoc.rdoc_files.include('lib/**/*.rb')
16
+ end
17
+
18
+ desc "Run all specs in spec directory"
19
+ Spec::Rake::SpecTask.new(:spec) do |t|
20
+ t.spec_files = FileList['spec/**/*_spec.rb']
21
+ end
22
+
23
+ Jeweler::Tasks.new do |gemspec|
24
+ gemspec.name = "bullet"
25
+ gemspec.summary = "A plugin to kill N+1 queries and unused eager loading"
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
+ gemspec.email = "flyerhzm@gmail.com"
28
+ gemspec.homepage = "http://github.com/flyerhzm/bullet"
29
+ gemspec.authors = ["Richard Huang"]
30
+ gemspec.files.exclude '.gitignore'
31
+ gemspec.files.exclude 'log/*'
32
+ end
33
+ Jeweler::GemcutterTasks.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.6.0
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bullet}
8
+ s.version = "1.6.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Richard Huang"]
12
+ s.date = %q{2009-10-08}
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
+ s.email = %q{flyerhzm@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.textile"
17
+ ]
18
+ s.files = [
19
+ "MIT-LICENSE",
20
+ "README.textile",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bullet.gemspec",
24
+ "lib/bullet.rb",
25
+ "lib/bullet/action_controller.rb",
26
+ "lib/bullet/active_record.rb",
27
+ "lib/bullet/association.rb",
28
+ "lib/bullet/counter.rb",
29
+ "lib/bullet/logger.rb",
30
+ "lib/bullet/notification.rb",
31
+ "lib/bulletware.rb",
32
+ "rails/init.rb",
33
+ "spec/bullet_association_spec.rb",
34
+ "spec/bullet_counter_spec.rb",
35
+ "spec/spec.opts",
36
+ "spec/spec_helper.rb",
37
+ "tasks/bullet_tasks.rake"
38
+ ]
39
+ s.homepage = %q{http://github.com/flyerhzm/bullet}
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.5}
43
+ s.summary = %q{A plugin to kill N+1 queries and unused eager loading}
44
+ s.test_files = [
45
+ "spec/bullet_counter_spec.rb",
46
+ "spec/spec_helper.rb",
47
+ "spec/bullet_association_spec.rb"
48
+ ]
49
+
50
+ if s.respond_to? :specification_version then
51
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
52
+ s.specification_version = 3
53
+
54
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
55
+ else
56
+ end
57
+ else
58
+ end
59
+ end