bullet 4.0.0 → 4.13.1

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.
Files changed (112) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -1
  4. data/.travis.yml +17 -6
  5. data/CHANGELOG.md +86 -0
  6. data/Gemfile +13 -12
  7. data/Gemfile.mongoid +14 -0
  8. data/Gemfile.mongoid-2.4 +19 -0
  9. data/Gemfile.mongoid-2.5 +19 -0
  10. data/Gemfile.mongoid-2.6 +19 -0
  11. data/Gemfile.mongoid-2.7 +19 -0
  12. data/Gemfile.mongoid-2.8 +19 -0
  13. data/Gemfile.mongoid-3.0 +19 -0
  14. data/Gemfile.mongoid-3.1 +19 -0
  15. data/Gemfile.mongoid-4.0 +19 -0
  16. data/Gemfile.rails-3.0 +19 -0
  17. data/Gemfile.rails-3.1 +19 -0
  18. data/Gemfile.rails-3.2 +19 -0
  19. data/Gemfile.rails-4.0 +19 -0
  20. data/Gemfile.rails-4.1 +19 -0
  21. data/{Hacking.textile → Hacking.md} +10 -5
  22. data/README.md +432 -0
  23. data/Rakefile +13 -6
  24. data/bullet.gemspec +6 -3
  25. data/lib/bullet/active_record3.rb +49 -11
  26. data/lib/bullet/active_record3x.rb +134 -0
  27. data/lib/bullet/{active_record31.rb → active_record4.rb} +45 -15
  28. data/lib/bullet/active_record41.rb +128 -0
  29. data/lib/bullet/dependency.rb +81 -0
  30. data/lib/bullet/detector/association.rb +24 -68
  31. data/lib/bullet/detector/base.rb +0 -6
  32. data/lib/bullet/detector/counter_cache.rb +59 -0
  33. data/lib/bullet/detector/n_plus_one_query.rb +56 -15
  34. data/lib/bullet/detector/unused_eager_loading.rb +84 -0
  35. data/lib/bullet/detector.rb +2 -2
  36. data/lib/bullet/ext/object.rb +10 -2
  37. data/lib/bullet/{mongoid.rb → mongoid2x.rb} +18 -18
  38. data/lib/bullet/mongoid3x.rb +56 -0
  39. data/lib/bullet/mongoid4x.rb +56 -0
  40. data/lib/bullet/notification/base.rb +22 -4
  41. data/lib/bullet/notification/n_plus_one_query.rb +1 -1
  42. data/lib/bullet/notification.rb +2 -0
  43. data/lib/bullet/rack.rb +51 -17
  44. data/lib/bullet/registry/base.rb +1 -1
  45. data/lib/bullet/registry/object.rb +4 -4
  46. data/lib/bullet/version.rb +1 -2
  47. data/lib/bullet.rb +119 -39
  48. data/perf/benchmark.rb +1 -4
  49. data/spec/bullet/detector/association_spec.rb +2 -70
  50. data/spec/bullet/detector/base_spec.rb +0 -6
  51. data/spec/bullet/detector/counter_cache_spec.rb +56 -0
  52. data/spec/bullet/detector/n_plus_one_query_spec.rb +78 -34
  53. data/spec/bullet/detector/unused_eager_loading_spec.rb +88 -0
  54. data/spec/bullet/ext/object_spec.rb +18 -7
  55. data/spec/bullet/ext/string_spec.rb +2 -2
  56. data/spec/bullet/notification/base_spec.rb +68 -15
  57. data/spec/bullet/notification/counter_cache_spec.rb +2 -2
  58. data/spec/bullet/notification/n_plus_one_query_spec.rb +4 -3
  59. data/spec/bullet/notification/unused_eager_loading_spec.rb +2 -2
  60. data/spec/bullet/notification_collector_spec.rb +4 -4
  61. data/spec/bullet/rack_spec.rb +40 -39
  62. data/spec/bullet/registry/association_spec.rb +3 -3
  63. data/spec/bullet/registry/base_spec.rb +6 -6
  64. data/spec/bullet/registry/object_spec.rb +4 -5
  65. data/spec/bullet_spec.rb +41 -0
  66. data/spec/integration/active_record3/association_spec.rb +662 -0
  67. data/spec/integration/active_record4/association_spec.rb +660 -0
  68. data/spec/integration/counter_cache_spec.rb +63 -0
  69. data/spec/integration/mongoid/association_spec.rb +190 -208
  70. data/spec/models/category.rb +1 -1
  71. data/spec/models/client.rb +1 -1
  72. data/spec/models/comment.rb +2 -2
  73. data/spec/models/document.rb +2 -2
  74. data/spec/models/firm.rb +1 -1
  75. data/spec/models/mongoid/address.rb +2 -0
  76. data/spec/models/mongoid/category.rb +2 -0
  77. data/spec/models/mongoid/comment.rb +2 -0
  78. data/spec/models/mongoid/company.rb +2 -0
  79. data/spec/models/mongoid/entry.rb +2 -0
  80. data/spec/models/mongoid/post.rb +4 -0
  81. data/spec/models/mongoid/user.rb +5 -0
  82. data/spec/models/newspaper.rb +1 -1
  83. data/spec/models/pet.rb +1 -1
  84. data/spec/models/post.rb +6 -13
  85. data/spec/spec_helper.rb +60 -25
  86. data/spec/support/bullet_ext.rb +1 -1
  87. data/spec/support/mongo_seed.rb +26 -2
  88. data/spec/support/sqlite_seed.rb +0 -6
  89. data/test.sh +15 -0
  90. metadata +77 -54
  91. data/.rvmrc +0 -2
  92. data/.rvmrc.example +0 -2
  93. data/Gemfile.lock +0 -132
  94. data/Gemfile.rails-2.3.14 +0 -16
  95. data/Gemfile.rails-2.3.14.lock +0 -65
  96. data/Gemfile.rails-3.0.12 +0 -17
  97. data/Gemfile.rails-3.0.12.lock +0 -116
  98. data/Gemfile.rails-3.1.4 +0 -18
  99. data/Gemfile.rails-3.1.4.lock +0 -134
  100. data/README.textile +0 -395
  101. data/README_for_rails2.textile +0 -400
  102. data/lib/bullet/action_controller2.rb +0 -49
  103. data/lib/bullet/active_record2.rb +0 -121
  104. data/lib/bullet/detector/counter.rb +0 -48
  105. data/lib/bullet/detector/unused_eager_association.rb +0 -42
  106. data/spec/bullet/detector/counter_spec.rb +0 -64
  107. data/spec/bullet/detector/unused_eager_association_spec.rb +0 -62
  108. data/spec/integration/association_spec.rb +0 -593
  109. data/spec/integration/counter_spec.rb +0 -39
  110. data/spec/integration/rails2/association_spec.rb +0 -593
  111. data/spec/integration/rails2/counter_spec.rb +0 -39
  112. data/test.result +0 -2293
data/README.md ADDED
@@ -0,0 +1,432 @@
1
+ # Bullet
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/bullet.png)](http://badge.fury.io/rb/bullet)
4
+ [![Build Status](https://secure.travis-ci.org/flyerhzm/bullet.png)](http://travis-ci.org/flyerhzm/bullet)
5
+ [![Coverage Status](https://coveralls.io/repos/flyerhzm/bullet/badge.png?branch=master)](https://coveralls.io/r/flyerhzm/bullet)
6
+ <a href="https://codeclimate.com/github/flyerhzm/bullet"><img src="https://codeclimate.com/github/flyerhzm/bullet.png" /></a>
7
+ [![Coderwall Endorse](http://api.coderwall.com/flyerhzm/endorsecount.png)](http://coderwall.com/flyerhzm)
8
+
9
+ The Bullet 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.
10
+
11
+ 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.
12
+
13
+ Bullet gem now supports **activerecord** >= 3.0 and **mongoid** >= 2.4.1.
14
+
15
+ If you use activerecord 2.x, please use bullet <= 4.5.0
16
+
17
+ ## External Introduction
18
+
19
+ * [http://railscasts.com/episodes/372-bullet](http://railscasts.com/episodes/372-bullet)
20
+ * [http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009](http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009)
21
+ * [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)
22
+ * [http://weblog.rubyonrails.org/2009/10/22/community-highlights](http://weblog.rubyonrails.org/2009/10/22/community-highlights)
23
+
24
+ ## Install
25
+
26
+ You can install it as a gem:
27
+
28
+ ```
29
+ gem install bullet
30
+ ```
31
+
32
+ or add it into a Gemfile (Bundler):
33
+
34
+
35
+ ```ruby
36
+ gem "bullet", :group => "development"
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ Bullet won't do ANYTHING unless you tell it to explicitly. Append to
42
+ `config/environments/development.rb` initializer with the following code:
43
+
44
+ ```ruby
45
+ config.after_initialize do
46
+ Bullet.enable = true
47
+ Bullet.alert = true
48
+ Bullet.bullet_logger = true
49
+ Bullet.console = true
50
+ Bullet.growl = true
51
+ Bullet.xmpp = { :account => 'bullets_account@jabber.org',
52
+ :password => 'bullets_password_for_jabber',
53
+ :receiver => 'your_account@jabber.org',
54
+ :show_online_status => true }
55
+ Bullet.rails_logger = true
56
+ Bullet.bugsnag = true
57
+ Bullet.airbrake = true
58
+ Bullet.add_footer = true
59
+ Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
60
+ end
61
+ ```
62
+
63
+ The notifier of Bullet is a wrap of [uniform_notifier](https://github.com/flyerhzm/uniform_notifier)
64
+
65
+ The code above will enable all seven of the Bullet notification systems:
66
+ * `Bullet.enable`: enable Bullet gem, otherwise do nothing
67
+ * `Bullet.alert`: pop up a JavaScript alert in the browser
68
+ * `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)
69
+ * `Bullet.rails_logger`: add warnings directly to the Rails log
70
+ * `Bullet.airbrake`: add notifications to airbrake
71
+ * `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
72
+ * `Bullet.growl`: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
73
+ * `Bullet.xmpp`: send XMPP/Jabber notifications to the receiver indicated. Note that the code will currently not handle the adding of contacts, so you will need to make both accounts indicated know each other manually before you will receive any notifications. If you restart the development server frequently, the 'coming online' sound for the Bullet account may start to annoy - in this case set :show_online_status to false; you will still get notifications, but the Bullet account won't announce it's online status anymore.
74
+ * `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries
75
+ * `Bullet.add_footer`: adds the details in the bottom left corner of the page
76
+ * `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
77
+
78
+ Bullet also allows you to disable any of its detectors.
79
+
80
+ ```ruby
81
+ # Each of these settings defaults to true
82
+
83
+ # Detect N+1 queries
84
+ Bullet.n_plus_one_query_enable = false
85
+
86
+ # Detect eager-loaded associations which are not used
87
+ Bullet.unused_eager_loading_enable = false
88
+
89
+ # Detect unnecessary COUNT queries which could be avoided
90
+ # with a counter_cache
91
+ Bullet.counter_cache_enable = false
92
+ ```
93
+
94
+ ## Whitelist
95
+
96
+ Sometimes Bullet may notify you of query problems you don't care to fix, or
97
+ which come from outside your code. You can whitelist these to ignore them:
98
+
99
+ ```ruby
100
+ Bullet.add_whitelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
101
+ Bullet.add_whitelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
102
+ Bullet.add_whitelist :type => :counter_cache, :class_name => "Country", :association => :cities
103
+ ```
104
+
105
+ ## Log
106
+
107
+ The Bullet log `log/bullet.log` will look something like this:
108
+
109
+ * N+1 Query:
110
+
111
+ ```
112
+ 2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts; model: Post => associations: [comments]·
113
+ Add to your finder: :include => [:comments]
114
+ 2009-08-25 20:40:17[INFO] N+1 Query: method call stack:·
115
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
116
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
117
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
118
+ /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
119
+ ```
120
+
121
+ 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.
122
+
123
+ * Unused eager loading:
124
+
125
+ ```
126
+ 2009-08-25 20:53:56[INFO] Unused eager loadings: PATH_INFO: /posts; model: Post => associations: [comments]·
127
+ Remove from your finder: :include => [:comments]
128
+ ```
129
+
130
+ These two lines are notifications that unused eager loadings have been encountered.
131
+
132
+ * Need counter cache:
133
+
134
+ ```
135
+ 2009-09-11 09:46:50[INFO] Need Counter Cache
136
+ Post => [:comments]
137
+ ```
138
+
139
+ ## Growl, XMPP/Jabber and Airbrake Support
140
+
141
+ see [https://github.com/flyerhzm/uniform_notifier](https://github.com/flyerhzm/uniform_notifier)
142
+
143
+ ## Important
144
+
145
+ If you find Bullet does not work for you, *please disable your browser's cache*.
146
+
147
+ ## Advanced
148
+
149
+ ### Profile a job
150
+
151
+ The Bullet gem uses rack middleware to profile requests. If you want to use Bullet without an http server, like to profile a job, you can use use profile method and fetch warnings
152
+
153
+ ```ruby
154
+ Bullet.profile do
155
+ # do anything
156
+ end
157
+ warnings = Bullet.warnings
158
+ ```
159
+
160
+ ### Work with sinatra
161
+
162
+ Configure and use `Bullet::Rack`
163
+
164
+ ```ruby
165
+ configure :development do
166
+ Bullet.enable = true
167
+ Bullet.bullet_logger = true
168
+ use Bullet::Rack
169
+ end
170
+ ```
171
+
172
+ ### Run in tests
173
+
174
+ First you need to enable Bullet in test environment.
175
+
176
+ ```ruby
177
+ # config/environments/test.rb
178
+ config.after_initialize do
179
+ Bullet.enable = true
180
+ Bullet.bullet_logger = true
181
+ Bullet.raise = true # raise an error if n+1 query occurs
182
+ end
183
+ ```
184
+
185
+ Then wrap each test in Bullet api.
186
+
187
+ ```ruby
188
+ # spec/spec_helper.rb
189
+ if Bullet.enable?
190
+ config.before(:each) do
191
+ Bullet.start_request
192
+ end
193
+
194
+ config.after(:each) do
195
+ Bullet.perform_out_of_channel_notifications if Bullet.notification?
196
+ Bullet.end_request
197
+ end
198
+ end
199
+ ```
200
+
201
+ ## Debug Mode
202
+
203
+ Bullet outputs some details info, to enable debug mode, set
204
+ `BULLET_DEBUG=true` env.
205
+
206
+ ## Contributors
207
+
208
+ [https://github.com/flyerhzm/bullet/contributors](https://github.com/flyerhzm/bullet/contributors)
209
+
210
+ ## Demo
211
+
212
+ Bullet is designed to function as you browse through your application in development. To see it in action, follow these steps to create, detect, and fix example query problems.
213
+
214
+ 1\. Create an example application
215
+
216
+ ```
217
+ $ rails new test_bullet
218
+ $ cd test_bullet
219
+ $ rails g scaffold post name:string
220
+ $ rails g scaffold comment name:string post_id:integer
221
+ $ bundle exec rake db:migrate
222
+ ```
223
+
224
+ 2\. Change `app/model/post.rb` and `app/model/comment.rb`
225
+
226
+ ```ruby
227
+ class Post < ActiveRecord::Base
228
+ has_many :comments
229
+ end
230
+
231
+ class Comment < ActiveRecord::Base
232
+ belongs_to :post
233
+ end
234
+ ```
235
+
236
+ 3\. Go to `rails c` and execute
237
+
238
+ ```ruby
239
+ post1 = Post.create(:name => 'first')
240
+ post2 = Post.create(:name => 'second')
241
+ post1.comments.create(:name => 'first')
242
+ post1.comments.create(:name => 'second')
243
+ post2.comments.create(:name => 'third')
244
+ post2.comments.create(:name => 'fourth')
245
+ ```
246
+
247
+ 4\. Change the `app/views/posts/index.html.erb` to produce a N+1 query
248
+
249
+ ```
250
+ <% @posts.each do |post| %>
251
+ <tr>
252
+ <td><%= post.name %></td>
253
+ <td><%= post.comments.map(&:name) %></td>
254
+ <td><%= link_to 'Show', post %></td>
255
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
256
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
257
+ </tr>
258
+ <% end %>
259
+ ```
260
+
261
+ 5\. Add the `bullet` gem to the `Gemfile`
262
+
263
+ ```ruby
264
+ gem "bullet"
265
+ ```
266
+
267
+ And run
268
+
269
+ ```
270
+ bundle install
271
+ ```
272
+
273
+ 6\. enable the Bullet gem in development, add a line to
274
+ `config/environments/development.rb`
275
+
276
+ ```ruby
277
+ config.after_initialize do
278
+ Bullet.enable = true
279
+ Bullet.alert = true
280
+ Bullet.bullet_logger = true
281
+ Bullet.console = true
282
+ # Bullet.growl = true
283
+ Bullet.rails_logger = true
284
+ Bullet.add_footer = true
285
+ end
286
+ ```
287
+
288
+ 7\. Start the server
289
+
290
+ ```
291
+ $ rails s
292
+ ```
293
+
294
+ 8\. Visit `http://localhost:3000/posts` in browser, and you will see a popup alert box that says
295
+
296
+ ```
297
+ The request has unused preload associations as follows:
298
+ None
299
+ The request has N+1 queries as follows:
300
+ model: Post => associations: [comment]
301
+ ```
302
+
303
+ which means there is a N+1 query from the Post object to its Comment association.
304
+
305
+ In the meanwhile, there's a log appended into `log/bullet.log` file
306
+
307
+ ```
308
+ 2010-03-07 14:12:18[INFO] N+1 Query in /posts
309
+ Post => [:comments]
310
+ Add to your finder: :include => [:comments]
311
+ 2010-03-07 14:12:18[INFO] N+1 Query method call stack
312
+ /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:14:in `_render_template__600522146_80203160_0'
313
+ /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `each'
314
+ /home/flyerhzm/Downloads/test_bullet/app/views/posts/index.html.erb:11:in `_render_template__600522146_80203160_0'
315
+ /home/flyerhzm/Downloads/test_bullet/app/controllers/posts_controller.rb:7:in `index'
316
+ ```
317
+
318
+ The generated SQL is:
319
+
320
+ ```
321
+ Post Load (1.0ms) SELECT * FROM "posts"
322
+ Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments".post_id = 1)
323
+ Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments".post_id = 2)
324
+ ```
325
+
326
+ 9\. To fix the N+1 query, change `app/controllers/posts_controller.rb` file
327
+
328
+ ```ruby
329
+ def index
330
+ @posts = Post.includes(:comments)
331
+
332
+ respond_to do |format|
333
+ format.html # index.html.erb
334
+ format.xml { render :xml => @posts }
335
+ end
336
+ end
337
+ ```
338
+
339
+ 10\. Refresh `http://localhost:3000/posts`. Now there's no alert box and nothing new in the log.
340
+
341
+ The generated SQL is:
342
+
343
+ ```
344
+ Post Load (0.5ms) SELECT * FROM "posts"
345
+ Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id IN (1,2))
346
+ ```
347
+
348
+ N+1 query fixed. Cool!
349
+
350
+ 11\. Now simulate unused eager loading. Change
351
+ `app/controllers/posts_controller.rb` and
352
+ `app/views/posts/index.html.erb`
353
+
354
+ ```ruby
355
+ def index
356
+ @posts = Post.includes(:comments)
357
+
358
+ respond_to do |format|
359
+ format.html # index.html.erb
360
+ format.xml { render :xml => @posts }
361
+ end
362
+ end
363
+ ```
364
+
365
+ ```
366
+ <% @posts.each do |post| %>
367
+ <tr>
368
+ <td><%= post.name %></td>
369
+ <td><%= link_to 'Show', post %></td>
370
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
371
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
372
+ </tr>
373
+ <% end %>
374
+ ```
375
+
376
+ 12\. Refresh `http://localhost:3000/posts`, and you will see a popup alert box that says
377
+
378
+ ```
379
+ The request has unused preload associations as follows:
380
+ model: Post => associations: [comment]
381
+ The request has N+1 queries as follows:
382
+ None
383
+ ```
384
+
385
+ Meanwhile, there's a line appended to `log/bullet.log`
386
+
387
+ ```
388
+ 2009-08-25 21:13:22[INFO] Unused preload associations: PATH_INFO: /posts; model: Post => associations: [comments]·
389
+ Remove from your finder: :include => [:comments]
390
+ ```
391
+
392
+ 13\. Simulate counter_cache. Change `app/controllers/posts_controller.rb`
393
+ and `app/views/posts/index.html.erb`
394
+
395
+ ```ruby
396
+ def index
397
+ @posts = Post.all
398
+
399
+ respond_to do |format|
400
+ format.html # index.html.erb
401
+ format.xml { render :xml => @posts }
402
+ end
403
+ end
404
+ ```
405
+
406
+ ```
407
+ <% @posts.each do |post| %>
408
+ <tr>
409
+ <td><%= post.name %></td>
410
+ <td><%= post.comments.size %></td>
411
+ <td><%= link_to 'Show', post %></td>
412
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
413
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
414
+ </tr>
415
+ <% end %>
416
+ ```
417
+
418
+ 14\. Refresh `http://localhost:3000/posts`, then you will see a popup alert box that says
419
+
420
+ ```
421
+ Need counter cache
422
+ Post => [:comments]
423
+ ```
424
+
425
+ Meanwhile, there's a line appended to `log/bullet.log`
426
+
427
+ ```
428
+ 2009-09-11 10:07:10[INFO] Need Counter Cache
429
+ Post => [:comments]
430
+ ```
431
+
432
+ Copyright (c) 2009 - 2014 Richard Huang (flyerhzm@gmail.com), released under the MIT license
data/Rakefile CHANGED
@@ -3,7 +3,6 @@ require "bundler"
3
3
  Bundler.setup
4
4
 
5
5
  require "rake"
6
- require "rdoc/task"
7
6
  require "rspec"
8
7
  require "rspec/core/rake_task"
9
8
 
@@ -35,11 +34,19 @@ RSpec::Core::RakeTask.new('spec:progress') do |spec|
35
34
  spec.pattern = "spec/**/*_spec.rb"
36
35
  end
37
36
 
38
- Rake::RDocTask.new do |rdoc|
39
- rdoc.rdoc_dir = "rdoc"
40
- rdoc.title = "bullet #{Bullet::VERSION}"
41
- rdoc.rdoc_files.include("README*")
42
- rdoc.rdoc_files.include("lib/**/*.rb")
37
+
38
+ begin
39
+ require 'rdoc/task'
40
+
41
+ desc "Generate documentation for the plugin."
42
+ Rake::RDocTask.new do |rdoc|
43
+ rdoc.rdoc_dir = "rdoc"
44
+ rdoc.title = "bullet #{Bullet::VERSION}"
45
+ rdoc.rdoc_files.include("README*")
46
+ rdoc.rdoc_files.include("lib/**/*.rb")
47
+ end
48
+ rescue LoadError
49
+ puts 'RDocTask is not supported for this platform'
43
50
  end
44
51
 
45
52
  task :default => :spec
data/bullet.gemspec CHANGED
@@ -10,12 +10,15 @@ Gem::Specification.new do |s|
10
10
  s.authors = ["Richard Huang"]
11
11
  s.email = ["flyerhzm@gmail.com"]
12
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."
13
+ s.summary = "help to kill N+1 queries and unused eager loading."
14
+ s.description = "help to kill N+1 queries and unused eager loading."
15
+
16
+ s.license = 'MIT'
15
17
 
16
18
  s.required_rubygems_version = ">= 1.3.6"
17
19
 
18
- s.add_dependency "uniform_notifier", "~> 1.0.0"
20
+ s.add_runtime_dependency "activesupport", ">= 3.0.0"
21
+ s.add_runtime_dependency "uniform_notifier", ">= 1.6.0"
19
22
 
20
23
  s.files = `git ls-files`.split("\n")
21
24
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -9,11 +9,11 @@ module Bullet
9
9
  def to_a
10
10
  records = origin_to_a
11
11
  if records.size > 1
12
- Bullet::Detector::Association.add_possible_objects(records)
13
- Bullet::Detector::Counter.add_possible_objects(records)
12
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
13
+ Bullet::Detector::CounterCache.add_possible_objects(records)
14
14
  elsif records.size == 1
15
- Bullet::Detector::Association.add_impossible_object(records.first)
16
- Bullet::Detector::Counter.add_impossible_object(records.first)
15
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
16
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
17
17
  end
18
18
  records
19
19
  end
@@ -29,7 +29,7 @@ module Bullet
29
29
  records.each do |record|
30
30
  Bullet::Detector::Association.add_object_associations(record, associations)
31
31
  end
32
- Bullet::Detector::Association.add_eager_loadings(records, associations)
32
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
33
33
  origin_preload_associations(records, associations, preload_options={})
34
34
  end
35
35
  end
@@ -42,21 +42,39 @@ module Bullet
42
42
  associations = (@eager_load_values + @includes_values).uniq
43
43
  records.each do |record|
44
44
  Bullet::Detector::Association.add_object_associations(record, associations)
45
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
46
45
  end
47
- Bullet::Detector::Association.add_eager_loadings(records, associations)
46
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
48
47
  records
49
48
  end
50
49
  end
51
50
 
52
51
  ::ActiveRecord::Associations::ClassMethods::JoinDependency.class_eval do
52
+ alias_method :origin_instantiate, :instantiate
53
53
  alias_method :origin_construct_association, :construct_association
54
+
55
+ def instantiate(rows)
56
+ @bullet_eager_loadings = {}
57
+ records = origin_instantiate(rows)
58
+
59
+ @bullet_eager_loadings.each do |klazz, eager_loadings_hash|
60
+ objects = eager_loadings_hash.keys
61
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
62
+ end
63
+ records
64
+ end
65
+
54
66
  # call join associations
55
67
  def construct_association(record, join, row)
68
+ result = origin_construct_association(record, join, row)
69
+
56
70
  associations = join.reflection.name
57
71
  Bullet::Detector::Association.add_object_associations(record, associations)
58
72
  Bullet::Detector::NPlusOneQuery.call_association(record, associations)
59
- origin_construct_association(record, join, row)
73
+ @bullet_eager_loadings[record.class] ||= {}
74
+ @bullet_eager_loadings[record.class][record] ||= Set.new
75
+ @bullet_eager_loadings[record.class][record] << associations
76
+
77
+ result
60
78
  end
61
79
  end
62
80
 
@@ -79,6 +97,18 @@ module Bullet
79
97
  Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
80
98
  origin_last(*args)
81
99
  end
100
+
101
+ alias_method :origin_empty?, :empty?
102
+ def empty?
103
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
104
+ origin_empty?
105
+ end
106
+
107
+ alias_method :origin_include?, :include?
108
+ def include?(object)
109
+ Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
110
+ origin_include?(object)
111
+ end
82
112
  end
83
113
 
84
114
  ::ActiveRecord::Associations::AssociationProxy.class_eval do
@@ -88,9 +118,17 @@ module Bullet
88
118
  # avoid stack level too deep
89
119
  result = origin_load_target
90
120
  Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.any? { |c| c.include?("load_target") }
91
- Bullet::Detector::Association.add_possible_objects(result)
121
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
92
122
  result
93
123
  end
124
+
125
+ alias_method :origin_set_inverse_instance, :set_inverse_instance
126
+ def set_inverse_instance(record, instance)
127
+ if record && we_can_set_the_inverse_on_this?(record)
128
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(record, @reflection.inverse_of.name)
129
+ end
130
+ origin_set_inverse_instance(record, instance)
131
+ end
94
132
  end
95
133
 
96
134
  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
@@ -98,7 +136,7 @@ module Bullet
98
136
 
99
137
  def has_cached_counter?
100
138
  result = origin_has_cached_counter?
101
- Bullet::Detector::Counter.add_counter_cache(@owner, @reflection.name) unless result
139
+ Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
102
140
  result
103
141
  end
104
142
  end
@@ -107,7 +145,7 @@ module Bullet
107
145
  alias_method :origin_has_cached_counter?, :has_cached_counter?
108
146
  def has_cached_counter?
109
147
  result = origin_has_cached_counter?
110
- Bullet::Detector::Counter.add_counter_cache(@owner, @reflection.name) unless result
148
+ Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
111
149
  result
112
150
  end
113
151
  end