bullet 4.5.0 → 4.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,400 +0,0 @@
1
- h1. Bullet
2
-
3
- !https://secure.travis-ci.org/flyerhzm/bullet.png!:http://travis-ci.org/flyerhzm/bullet
4
-
5
- !http://api.coderwall.com/flyerhzm/endorsecount.png!:http://coderwall.com/flyerhzm
6
-
7
- 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.
8
-
9
- 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.
10
-
11
- The Bullet plugin/gem now supports rails 2.1, 2.2 and 2.3, tested in rails 2.1.2, 2.2.2 and 2.3.2.
12
-
13
- ****************************************************************************
14
-
15
- h2. Change
16
-
17
- 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.
18
-
19
- ****************************************************************************
20
-
21
- h2. Contributors
22
-
23
- "https://github.com/flyerhzm/bullet/contributors":https://github.com/flyerhzm/bullet/contributors
24
-
25
- ****************************************************************************
26
-
27
- h2. Install
28
-
29
- You can install it as a gem:
30
- <pre><code>
31
- sudo gem install bullet
32
- </code></pre>
33
-
34
- ****************************************************************************
35
-
36
- h2. Configuration
37
-
38
- Bullet won't do ANYTHING unless you tell it to explicitly. Append to <code>config/environments/development.rb</code> initializer with the following code:
39
- <pre><code>
40
- config.after_initialize do
41
- Bullet.enable = true
42
- Bullet.alert = true
43
- Bullet.bullet_logger = true
44
- Bullet.console = true
45
- Bullet.growl = true
46
- Bullet.rails_logger = true
47
- Bullet.xmpp = { :account => 'bullets_account@jabber.org',
48
- :password => 'bullets_password_for_jabber',
49
- :receiver => 'your_account@jabber.org',
50
- :show_online_status => true }
51
- end
52
- </code></pre>
53
-
54
- The notifier of bullet is a wrap of "uniform_notifier":https://github.com/flyerhzm/uniform_notifier
55
-
56
- The code above will enable all five of the Bullet notification systems:
57
- * <code>Bullet.enable</code>: enable Bullet plugin/gem, otherwise do nothing
58
- * <code>Bullet.alert</code>: pop up a JavaScript alert in the browser
59
- * <code>Bullet.bullet_logger</code>: log to the Bullet log file (RAILS_ROOT/log/bullet.log)
60
- * <code>Bullet.rails_logger</code>: add warnings directly to the Rails log
61
- * <code>Bullet.console</code>: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
62
- * <code>Bullet.growl</code>: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
63
-
64
- ****************************************************************************
65
-
66
- h2. Log
67
-
68
- The Bullet log <code>log/bullet.log</code> will look something like this:
69
-
70
- * N+1 Query:
71
- <pre><code>
72
- 2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts; model: Post => associations: [comments]·
73
- Add to your finder: :include => [:comments]
74
- 2009-08-25 20:40:17[INFO] N+1 Query: method call stack:·
75
- /Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
76
- /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
77
- /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
78
- /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
79
- </code></pre>
80
-
81
- 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.
82
-
83
- * Unused eager loading:
84
- <pre><code>
85
- 2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts; model: Post => associations: [comments]·
86
- Remove from your finder: :include => [:comments]
87
- </code></pre>
88
-
89
- These two lines are notifications that unused eager loadings have been encountered.
90
-
91
- * Need counter cache:
92
- <pre><code>
93
- 2009-09-11 09:46:50[INFO] Need Counter Cache
94
- Post => [:comments]
95
- </code></pre>
96
-
97
- ****************************************************************************
98
-
99
- h2. Growl Support
100
-
101
- To get Growl support up-and-running for Bullet, follow the steps below:
102
- * Install the ruby-growl gem: <code>gem install ruby-growl</code>
103
- * Open the Growl preference pane in Systems Preferences
104
- * Click the "Network" tab
105
- * 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 => 'growl password' }</code> in the config file.
106
- * Restart Growl ("General" tab -> Stop Growl -> Start Growl)
107
- * 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.
108
-
109
- ****************************************************************************
110
-
111
- h2. Ruby 1.9 issue
112
-
113
- ruby-growl gem has an issue about md5 in ruby 1.9, if you use growl and ruby 1.9, check this gist http://gist.github.com/300184
114
-
115
- ****************************************************************************
116
-
117
- h2. XMPP/Jabber Support
118
-
119
- To get XMPP support up-and-running for Bullet, follow the steps below:
120
- * Install the xmpp4r gem: <code>sudo gem install xmpp4r</code>
121
- * Make both the bullet and the receipient account add each other as contacts.
122
- This will require you to manually log into both accounts, add each other
123
- as contact and confirm each others contact request.
124
- * Boot up your application. Bullet will automatically send an XMPP notification when XMPP is turned on.
125
-
126
- ****************************************************************************
127
-
128
- h2. Important
129
-
130
- If you encounter the following errors in development environment:
131
-
132
- <pre><code>
133
- You might have expected an instance of Array.
134
- The error occurred while evaluating nil.include?
135
- /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:142:in `create_time_zone_conversion_attribute?'
136
- /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:75:in `define_attribute_methods'
137
- /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `each'
138
- /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `define_attribute_methods'
139
- /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:242:in `method_missing'
140
- </code></pre>
141
-
142
- Or any strange behavior of bullet plugin/gem, *please disable your browser's cache*.
143
-
144
- ****************************************************************************
145
-
146
- h2. Advance
147
-
148
- 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:
149
-
150
- <pre><code>
151
- Bullet.start_request if Bullet.enable?
152
- # run job
153
- if Bullet.enable? && Bullet.notification?
154
- Bullet.perform_out_of_channel_notifications
155
- end
156
- Bullet.end_request if Bullet.enable?
157
- </code></pre>
158
-
159
- Or you want to use it in test mode
160
-
161
- <pre><code>
162
- before(:each)
163
- Bullet.start_request if Bullet.enable?
164
- end
165
-
166
- after(:each)
167
- if Bullet.enable? && Bullet.notification?
168
- Bullet.perform_out_of_channel_notifications
169
- end
170
- Bullet.end_request if Bullet.enable?
171
- end
172
- </code></pre>
173
-
174
- Don't forget enabling bullet in test environment.
175
-
176
- ****************************************************************************
177
-
178
- h2. Links
179
-
180
- * "http://weblog.rubyonrails.org/2009/10/22/community-highlights":http://weblog.rubyonrails.org/2009/10/22/community-highlights
181
- * "http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009":http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009
182
- * "http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1":http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1
183
-
184
- ****************************************************************************
185
-
186
- h2. Step by step example
187
-
188
- 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.
189
-
190
- 1. setup test environment
191
-
192
- <pre><code>
193
- $ rails test_bullet
194
- $ cd test_bullet
195
- $ script/generate scaffold post name:string
196
- $ script/generate scaffold comment name:string post_id:integer
197
- $ rake db:migrate
198
- </code></pre>
199
-
200
- 2. change <code>app/model/post.rb</code> and <code>app/model/comment.rb</code>
201
-
202
- <pre><code>
203
- class Post < ActiveRecord::Base
204
- has_many :comments
205
- end
206
-
207
- class Comment < ActiveRecord::Base
208
- belongs_to :post
209
- end
210
- </code></pre>
211
-
212
- 3. go to script/console and execute
213
-
214
- <pre><code>
215
- post1 = Post.create(:name => 'first')
216
- post2 = Post.create(:name => 'second')
217
- post1.comments.create(:name => 'first')
218
- post1.comments.create(:name => 'second')
219
- post2.comments.create(:name => 'third')
220
- post2.comments.create(:name => 'fourth')
221
- </code></pre>
222
-
223
- 4. change the <code>app/views/posts/index.html.erb</code> to produce a N+1 query
224
-
225
- <pre><code>
226
- <% @posts.each do |post| %>
227
- <tr>
228
- <td><%=h post.name %></td>
229
- <td><%= post.comments.collect(&:name) %></td>
230
- <td><%= link_to 'Show', post %></td>
231
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
232
- <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
233
- </tr>
234
- <% end %>
235
- </code></pre>
236
-
237
- 5. add bullet plugin
238
-
239
- <pre><code>
240
- $ script/plugin install git://github.com/flyerhzm/bullet.git
241
- </code></pre>
242
-
243
- 6. enable the bullet plugin in development, add a line to <code>config/environments/development.rb</code>
244
-
245
- <pre><code>
246
- config.after_initialize do
247
- Bullet.enable = true
248
- Bullet.alert = true
249
- Bullet.bullet_logger = true
250
- Bullet.console = true
251
- # Bullet.growl = true
252
- Bullet.rails_logger = true
253
- end
254
- </code></pre>
255
-
256
- 7. start server
257
-
258
- <pre><code>
259
- $ script/server
260
- </code></pre>
261
-
262
- 8. input http://localhost:3000/posts in browser, then you will see a popup alert box says
263
-
264
- <pre><code>
265
- The request has unused preload associations as follows:
266
- None
267
- The request has N+1 queries as follows:
268
- model: Post => associations: [comment]
269
- </code></pre>
270
-
271
- which means there is a N+1 query from post object to comments associations.
272
-
273
- In the meanwhile, there's a log appended into <code>log/bullet.log</code> file
274
-
275
- <pre><code>
276
- 2009-08-20 09:12:19[INFO] N+1 Query: PATH_INFO: /posts; model: Post => assocations: [comments]
277
- Add your finder: :include => [:comments]
278
- 2009-08-20 09:12:19[INFO] N+1 Query: method call stack:
279
- /Users/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
280
- /Users/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:8:in `each'
281
- /Users/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
282
- /Users/flyerhzm/Downloads/test_bullet/app/controllers/posts_controller.rb:7:in `index'
283
- </code></pre>
284
-
285
- The generated SQLs are
286
-
287
- <pre><code>
288
- Post Load (1.0ms) SELECT * FROM "posts"
289
- Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments".post_id = 1)
290
- Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments".post_id = 2)
291
- </code></pre>
292
-
293
-
294
- 9. fix the N+1 query, change <code>app/controllers/posts_controller.rb</code> file
295
-
296
- <pre><code>
297
- def index
298
- @posts = Post.find(:all, :include => :comments)
299
-
300
- respond_to do |format|
301
- format.html # index.html.erb
302
- format.xml { render :xml => @posts }
303
- end
304
- end
305
- </code></pre>
306
-
307
- 10. refresh http://localhost:3000/posts page, no alert box and no log appended.
308
-
309
- The generated SQLs are
310
-
311
- <pre><code>
312
- Post Load (0.5ms) SELECT * FROM "posts"
313
- Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id IN (1,2))
314
- </code></pre>
315
-
316
- a N+1 query fixed. Cool!
317
-
318
- 11. now simulate unused eager loading. Change <code>app/controllers/posts_controller.rb</code> and <code>app/views/posts/index.html.erb</code>
319
-
320
- <pre><code>
321
- def index
322
- @posts = Post.find(:all, :include => :comments)
323
-
324
- respond_to do |format|
325
- format.html # index.html.erb
326
- format.xml { render :xml => @posts }
327
- end
328
- end
329
- </code></pre>
330
-
331
- <pre><code>
332
- <% @posts.each do |post| %>
333
- <tr>
334
- <td><%=h post.name %></td>
335
- <td><%= link_to 'Show', post %></td>
336
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
337
- <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
338
- </tr>
339
- <% end %>
340
- </code></pre>
341
-
342
- 12. refresh http://localhost:3000/posts page, then you will see a popup alert box says
343
-
344
- <pre><code>
345
- The request has unused preload associations as follows:
346
- model: Post => associations: [comment]
347
- The request has N+1 queries as follows:
348
- None
349
- </code></pre>
350
-
351
- In the meanwhile, there's a log appended into <code>log/bullet.log</code> file
352
-
353
- <pre><code>
354
- 2009-08-25 21:13:22[INFO] Unused preload associations: PATH_INFO: /posts; model: Post => associations: [comments]·
355
- Remove from your finder: :include => [:comments]
356
- </code></pre>
357
-
358
- 13. simulate counter_cache. Change <code>app/controllers/posts_controller.rb</code> and <code>app/views/posts/index.html.erb</code>
359
-
360
- <pre><code>
361
- def index
362
- @posts = Post.find(:all)
363
-
364
- respond_to do |format|
365
- format.html # index.html.erb
366
- format.xml { render :xml => @posts }
367
- end
368
- end
369
- </code></pre>
370
-
371
- <pre><code>
372
- <% @posts.each do |post| %>
373
- <tr>
374
- <td><%=h post.name %></td>
375
- <td><%=h post.comments.size %></td>
376
- <td><%= link_to 'Show', post %></td>
377
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
378
- <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
379
- </tr>
380
- <% end %>
381
- </code></pre>
382
-
383
- 14. refresh http://localhost:3000/posts page, then you will see a popup alert box says
384
-
385
- <pre><code>
386
- Need counter cache
387
- Post => [:comments]
388
- </code></pre>
389
-
390
- In the meanwhile, there's a log appended into <code>log/bullet.log</code> file.
391
-
392
- <pre><code>
393
- 2009-09-11 10:07:10[INFO] Need Counter Cache
394
- Post => [:comments]
395
- </code></pre>
396
-
397
- ****************************************************************************
398
-
399
-
400
- Copyright (c) 2009 - 2013 Richard Huang (flyerhzm@gmail.com), released under the MIT license
@@ -1,50 +0,0 @@
1
- module Bullet
2
- class ActionController
3
- extend Dependency
4
-
5
- def self.enable
6
- require 'action_controller'
7
- if active_record23?
8
- ::ActionController::Dispatcher.middleware.use Bullet::Rack
9
- ::ActionController::Dispatcher.class_eval do
10
- class <<self
11
- alias_method :origin_reload_application, :reload_application
12
- def reload_application
13
- origin_reload_application
14
- Bullet.clear
15
- end
16
- end
17
- end
18
- elsif active_record21? || active_record22?
19
- ::ActionController::Dispatcher.class_eval do
20
- alias_method :origin_reload_application, :reload_application
21
- def reload_application
22
- origin_reload_application
23
- Bullet.clear
24
- end
25
- end
26
-
27
- ::ActionController::Base.class_eval do
28
- alias_method :origin_process, :process
29
- def process(request, response, method = :perform_action, *arguments)
30
- Bullet.start_request
31
- response = origin_process(request, response, method = :perform_action, *arguments)
32
-
33
- if Bullet.notification?
34
- if response.headers["type"] && response.headers["type"].include?('text/html') && response.body.include?("<html>")
35
- response.body <<= Bullet.gather_inline_notifications
36
- response.headers["Content-Length"] = response.body.bytesize.to_s
37
- end
38
-
39
- Bullet.perform_out_of_channel_notifications
40
- end
41
- Bullet.end_request
42
- response
43
- end
44
- end
45
- else
46
- puts "Gem Bullet: Unsupported rails version"
47
- end
48
- end
49
- end
50
- end
@@ -1,121 +0,0 @@
1
- module Bullet
2
- module ActiveRecord
3
- def self.enable
4
- require 'active_record'
5
- ::ActiveRecord::Base.class_eval do
6
- class << self
7
- alias_method :origin_find_every, :find_every
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 find_every(options)
11
- records = origin_find_every(options)
12
-
13
- if records
14
- if records.size > 1
15
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
16
- Bullet::Detector::CounterCache.add_possible_objects(records)
17
- elsif records.size == 1
18
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
19
- Bullet::Detector::CounterCache.add_impossible_object(records.first)
20
- end
21
- end
22
-
23
- records
24
- end
25
- end
26
- end
27
-
28
- ::ActiveRecord::AssociationPreload::ClassMethods.class_eval do
29
- alias_method :origin_preload_associations, :preload_associations
30
- # include query for one to many associations.
31
- # keep this eager loadings.
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::Detector::Association.add_object_associations(record, associations)
37
- end
38
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
39
- origin_preload_associations(records, associations, preload_options={})
40
- end
41
- end
42
-
43
- ::ActiveRecord::Associations::ClassMethods.class_eval do
44
- # add include in named_scope
45
- alias_method :origin_find_with_associations, :find_with_associations
46
- def find_with_associations(options)
47
- records = origin_find_with_associations(options)
48
- associations = merge_includes(scope(:find, :include), options[:include])
49
- records.each do |record|
50
- Bullet::Detector::Association.add_object_associations(record, associations)
51
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
52
- end
53
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
54
- records
55
- end
56
- end
57
-
58
- ::ActiveRecord::Associations::ClassMethods::JoinDependency.class_eval do
59
- # call join associations
60
- alias_method :origin_construct_association, :construct_association
61
- def construct_association(record, join, row)
62
- associations = join.reflection.name
63
- Bullet::Detector::Association.add_object_associations(record, associations)
64
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
65
- origin_construct_association(record, join, row)
66
- end
67
- end
68
-
69
- ::ActiveRecord::Associations::AssociationCollection.class_eval do
70
- # call one to many associations
71
- alias_method :origin_load_target, :load_target
72
- def load_target
73
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
74
- origin_load_target
75
- end
76
-
77
- alias_method :origin_first, :first
78
- def first(*args)
79
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
80
- origin_first(*args)
81
- end
82
-
83
- alias_method :origin_last, :last
84
- def last(*args)
85
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
86
- origin_last(*args)
87
- end
88
- end
89
-
90
- ::ActiveRecord::Associations::AssociationProxy.class_eval do
91
- # call has_one and belong_to association
92
- alias_method :origin_load_target, :load_target
93
- def load_target
94
- # avoid stack level too deep
95
- result = origin_load_target
96
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.any? {|c| c.include?("load_target") }
97
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
98
- result
99
- end
100
- end
101
-
102
- ::ActiveRecord::Associations::HasManyAssociation.class_eval do
103
- alias_method :origin_has_cached_counter?, :has_cached_counter?
104
- def has_cached_counter?
105
- result = origin_has_cached_counter?
106
- Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
107
- result
108
- end
109
- end
110
-
111
- ::ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
112
- alias_method :origin_has_cached_counter?, :has_cached_counter?
113
- def has_cached_counter?
114
- result = origin_has_cached_counter?
115
- Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
116
- result
117
- end
118
- end
119
- end
120
- end
121
- end