flyerhzm-bullet 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -1,6 +1,6 @@
1
1
  h1. Bullet
2
2
 
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.
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), when you're using eager loading that isn't necessary and when you should use counter cache.
4
4
 
5
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
6
 
@@ -32,11 +32,11 @@ Bullet won't do ANYTHING unless you tell it to explicitly. Append to <code>confi
32
32
  <pre><code>
33
33
  config.after_initialize do
34
34
  Bullet.enable = true
35
- Bullet::Association.alert = true
36
- Bullet::Association.bullet_logger = true
37
- Bullet::Association.console = true
38
- Bullet::Association.growl = true
39
- Bullet::Association.rails_logger = true
35
+ Bullet.alert = true
36
+ Bullet.bullet_logger = true
37
+ Bullet.console = true
38
+ Bullet.growl = true
39
+ Bullet.rails_logger = true
40
40
  end
41
41
  </code></pre>
42
42
 
@@ -44,18 +44,18 @@ It is recommended to config growl notification as follows if your collaborators
44
44
  <pre><code>
45
45
  begin
46
46
  require 'ruby-growl'
47
- Bullet::Association.growl = true
47
+ Bullet.growl = true
48
48
  rescue MissingSourceFile
49
49
  end
50
50
  </code></pre>
51
51
 
52
52
  The code above will enable all five of the Bullet notification systems:
53
53
  * <code>Bullet.enable</code>: enable Bullet plugin, otherwise do nothing
54
- * <code>Bullet::Association.alert</code>: pop up a JavaScript alert in the browser
55
- * <code>Bullet::Association.bullet_logger</code>: log to the Bullet log file (RAILS_ROOT/log/bullet.log)
56
- * <code>Bullet::Association.rails_logger</code>: add warnings directly to the Rails log
57
- * <code>Bullet::Association.console</code>: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
58
- * <code>Bullet::Association.growl</code>: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
54
+ * <code>Bullet.alert</code>: pop up a JavaScript alert in the browser
55
+ * <code>Bullet.bullet_logger</code>: log to the Bullet log file (RAILS_ROOT/log/bullet.log)
56
+ * <code>Bullet.rails_logger</code>: add warnings directly to the Rails log
57
+ * <code>Bullet.console</code>: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
58
+ * <code>Bullet.growl</code>: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
59
59
 
60
60
  ****************************************************************************
61
61
 
@@ -84,6 +84,12 @@ Remove from your finder: :include => [:comments]
84
84
 
85
85
  These two lines are notifications that unused eager loadings have been encountered.
86
86
 
87
+ * Need counter cache:
88
+ <pre><code>
89
+ 2009-09-11 09:46:50[INFO] Need Counter Cache
90
+ Post => [:comments]
91
+ </code></pre>
92
+
87
93
  ****************************************************************************
88
94
 
89
95
  h2. Growl Support
@@ -92,23 +98,41 @@ To get Growl support up-and-running for Bullet, follow the steps below:
92
98
  * Install the ruby-growl gem: <code>sudo gem install ruby-growl</code>
93
99
  * Open the Growl preference pane in Systems Preferences
94
100
  * Click the "Network" tab
95
- * 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.
101
+ * 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.
96
102
  * Restart Growl ("General" tab -> Stop Growl -> Start Growl)
97
103
  * 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.
98
104
 
99
105
  ****************************************************************************
100
106
 
107
+ h2. Important
108
+
109
+ If you encounter the following errors in development environment:
110
+
111
+ <pre><code>
112
+ You might have expected an instance of Array.
113
+ The error occurred while evaluating nil.include?
114
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:142:in `create_time_zone_conversion_attribute?'
115
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:75:in `define_attribute_methods'
116
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `each'
117
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:71:in `define_attribute_methods'
118
+ /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/attribute_methods.rb:242:in `method_missing'
119
+ </code></pre>
120
+
121
+ Or any strange behavior of bullet plugin/gem, *please disable your browser's cache*.
122
+
123
+ ****************************************************************************
124
+
101
125
  h2. Advance
102
126
 
103
127
  The bullet plugin use rack middleware for http request. If you want to bullet for without http server, such as job server. You can do like this:
104
128
 
105
129
  <pre><code>
106
- Bullet::Association.start_request if Bullet.enable?
130
+ Bullet.start_request if Bullet.enable?
107
131
  # run job
108
132
  if Bullet.enable?
109
- Bullet::Association.growl_notification
110
- Bullet::Association.log_notificatioin('JobServer: ')
111
- Bullet::Association.end_request
133
+ Bullet.growl_notification
134
+ Bullet.log_notificatioin('JobServer: ')
135
+ Bullet.end_request
112
136
  end
113
137
  </code></pre>
114
138
 
@@ -118,8 +142,6 @@ h2. Step by step example
118
142
 
119
143
  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.
120
144
 
121
- *Important*: It is strongly recommended you disable your browser's cache.
122
-
123
145
  1. setup test environment
124
146
 
125
147
  <pre><code>
@@ -178,8 +200,8 @@ $ script/plugin install git://github.com/flyerhzm/bullet.git
178
200
  <pre><code>
179
201
  config.after_initialize do
180
202
  Bullet.enable = true
181
- Bullet::Association.alert = true
182
- Bullet::Association.bullet_logger = true
203
+ Bullet.alert = true
204
+ Bullet.bullet_logger = true
183
205
  end
184
206
  </code></pre>
185
207
 
@@ -285,5 +307,44 @@ In the meanwhile, there's a log appended into <code>log/bullet.log</code> file
285
307
  Remove from your finder: :include => [:comments]
286
308
  </code></pre>
287
309
 
310
+ 13. simulate counter_cache. Change <code>app/controllers/posts_controller.rb</code> and <code>app/views/posts/index.html.erb</code>
311
+
312
+ <pre><code>
313
+ def index
314
+ @posts = Post.find(:all)
315
+
316
+ respond_to do |format|
317
+ format.html # index.html.erb
318
+ format.xml { render :xml => @posts }
319
+ end
320
+ end
321
+ </code></pre>
322
+
323
+ <pre><code>
324
+ <% @posts.each do |post| %>
325
+ <tr>
326
+ <td><%=h post.name %></td>
327
+ <td><%=h post.comments.size %></td>
328
+ <td><%= link_to 'Show', post %></td>
329
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
330
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
331
+ </tr>
332
+ <% end %>
333
+ </code></pre>
334
+
335
+ 14. refresh http://localhost:3000/posts page, then you will see a popup alert box says
336
+
337
+ <pre><code>
338
+ Need counter cache
339
+ Post => [:comments]
340
+ </code></pre>
341
+
342
+ In the meanwhile, there's a log appended into <code>log/bullet.log</code> file.
343
+
344
+ <pre><code>
345
+ 2009-09-11 10:07:10[INFO] Need Counter Cache
346
+ Post => [:comments]
347
+ </code></pre>
348
+
288
349
 
289
350
  Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com), released under the MIT license
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.1
1
+ 1.5.0
data/bullet.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{bullet}
8
- s.version = "1.4.1"
8
+ s.version = "1.5.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-09-09}
12
+ s.date = %q{2009-09-11}
13
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 = [
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
26
26
  "lib/bullet/association.rb",
27
27
  "lib/bullet/counter.rb",
28
28
  "lib/bullet/logger.rb",
29
+ "lib/bullet/notification.rb",
29
30
  "lib/bulletware.rb",
30
31
  "rails/init.rb",
31
32
  "spec/bullet_association_spec.rb",
@@ -40,9 +41,9 @@ Gem::Specification.new do |s|
40
41
  s.rubygems_version = %q{1.3.5}
41
42
  s.summary = %q{A plugin to kill N+1 queries and unused eager loading}
42
43
  s.test_files = [
43
- "spec/bullet_association_spec.rb",
44
- "spec/bullet_counter_spec.rb",
45
- "spec/spec_helper.rb"
44
+ "spec/bullet_counter_spec.rb",
45
+ "spec/spec_helper.rb",
46
+ "spec/bullet_association_spec.rb"
46
47
  ]
47
48
 
48
49
  if s.respond_to? :specification_version then
data/lib/bullet.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  require 'bulletware'
2
2
 
3
3
  module Bullet
4
- @@enable = nil
5
-
6
4
  class <<self
5
+ attr_accessor :enable, :alert, :console, :growl, :growl_password, :rails_logger, :bullet_logger, :logger, :logger_file
6
+
7
7
  def enable=(enable)
8
- @@enable = enable
8
+ @enable = enable
9
9
  if enable?
10
10
  Bullet::ActiveRecord.enable
11
11
  ActionController::Dispatcher.middleware.use Bulletware
@@ -13,11 +13,59 @@ module Bullet
13
13
  end
14
14
 
15
15
  def enable?
16
- @@enable == true
16
+ @enable == true
17
+ end
18
+
19
+ def growl=(growl)
20
+ if growl
21
+ begin
22
+ require 'ruby-growl'
23
+ growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password)
24
+ growl.notify('Bullet Notification', 'Bullet Notification', 'Bullet Growl notifications have been turned on')
25
+ rescue MissingSourceFile
26
+ raise NotificationError.new('You must install the ruby-growl gem to use Growl notifications: `sudo gem install ruby-growl`')
27
+ end
28
+ end
29
+ @growl = growl
30
+ end
31
+
32
+ def bullet_logger=(bullet_logger)
33
+ if @bullet_logger = bullet_logger
34
+ @logger_file = File.open(Bullet::BulletLogger::LOG_FILE, 'a+')
35
+ @logger = Bullet::BulletLogger.new(@logger_file)
36
+ end
37
+ end
38
+
39
+ BULLETS = [Bullet::Association, Bullet::Counter]
40
+
41
+ def start_request
42
+ BULLETS.each {|bullet| bullet.start_request}
43
+ end
44
+
45
+ def end_request
46
+ BULLETS.each {|bullet| bullet.end_request}
47
+ end
48
+
49
+ def notification?
50
+ BULLETS.any? {|bullet| bullet.notification?}
51
+ end
52
+
53
+ def javascript_notification
54
+ BULLETS.collect {|bullet| bullet.javascript_notification if bullet.notification?}.join("\n")
55
+ end
56
+
57
+ def growl_notification
58
+ BULLETS.each {|bullet| bullet.growl_notification if bullet.notification?}
59
+ end
60
+
61
+ def log_notification(path)
62
+ BULLETS.each {|bullet| bullet.log_notification(path) if bullet.notification?}
17
63
  end
18
64
  end
19
65
 
20
66
  autoload :ActiveRecord, 'bullet/active_record'
21
67
  autoload :Association, 'bullet/association'
68
+ autoload :Counter, 'bullet/counter'
22
69
  autoload :BulletLogger, 'bullet/logger'
70
+ autoload :Notification, 'bullet/notification'
23
71
  end
@@ -1,64 +1,12 @@
1
1
  module Bullet
2
- class BulletAssociationError < StandardError
3
- end
4
-
5
2
  class Association
6
3
  class <<self
7
- @@alert = nil
8
- @@bullet_logger = nil
9
- @@console = nil
10
- @@growl = nil
11
- @@growl_password = nil
12
- @@rails_logger = nil
13
-
14
- ###################################################
15
- # Configurations
16
- ###################################################
17
- def alert=(alert)
18
- @@alert = alert
19
- end
20
-
21
- def console=(console)
22
- @@console = console
23
- end
24
-
25
- def growl=(growl)
26
- if growl
27
- begin
28
- require 'ruby-growl'
29
- growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password)
30
- growl.notify('Bullet Notification', 'Bullet Notification', 'Bullet Growl notifications have been turned on')
31
- rescue MissingSourceFile
32
- raise BulletAssociationError.new('You must install the ruby-growl gem to use Growl notifications: `sudo gem install ruby-growl`')
33
- end
34
- end
35
- @@growl = growl
36
- end
37
-
38
- def growl_password=(growl_password)
39
- @@growl_password = growl_password
40
- end
4
+ include Bullet::Notification
41
5
 
42
- def bullet_logger=(bullet_logger)
43
- if @@bullet_logger = bullet_logger
44
- @@logger_file = File.open(Bullet::BulletLogger::LOG_FILE, 'a+')
45
- @@logger = Bullet::BulletLogger.new(@@logger_file)
46
- end
47
- end
48
-
49
- def rails_logger=(rails_logger)
50
- @@rails_logger = rails_logger
51
- end
52
-
53
- #####################################################
54
- # login control interface
55
- #####################################################
56
6
  def start_request
57
- # puts "start request"
58
7
  end
59
8
 
60
9
  def end_request
61
- # puts "end request"
62
10
  @@object_associations = nil
63
11
  @@unpreload_associations = nil
64
12
  @@unused_preload_associations = nil
@@ -69,110 +17,36 @@ module Bullet
69
17
  @@eager_loadings = nil
70
18
  end
71
19
 
72
- def has_bad_assocations?
20
+ def notification?
73
21
  check_unused_preload_associations
74
22
  has_unpreload_associations? or has_unused_preload_associations?
75
23
  end
76
24
 
77
- ######################################################
78
- # Notifications
79
- ######################################################
80
- def javascript_notification
81
- str = ''
82
- if @@alert || @@console
83
- response = notification_response
84
- end
85
- if @@alert
86
- str << wrap_js_association("alert(#{response.join("\n").inspect});")
87
- end
88
- if @@console
89
- title = []
90
- title << unused_preload_messages.first.first unless unused_preload_messages.empty?
91
- title << unpreload_messages.first.first unless unpreload_messages.empty?
92
- code = <<-CODE
93
- if (typeof(console) !== 'undefined') {
94
-
95
- if (console.groupCollapsed && console.groupEnd && console.log) {
96
-
97
- console.groupCollapsed(#{title.join(', ').inspect});
98
- console.log(#{response.join("\n").inspect});
99
- console.log(#{call_stack_messages.join("\n").inspect});
100
- console.groupEnd();
101
-
102
- } else if (console.log) {
103
-
104
- console.log(#{response.join("\n").inspect});
105
- }
106
- }
107
- CODE
108
- str << wrap_js_association(code)
109
- end
110
- str
111
- end
112
-
113
- def growl_notification
114
- if @@growl
115
- response = notification_response
116
- begin
117
- growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password)
118
- growl.notify('Bullet Notification', 'Bullet Notification', response.join("\n"))
119
- rescue
120
- end
121
- end
122
- end
123
-
124
- def log_notificatioin(path)
125
- if (@@bullet_logger || @@rails_logger) && (!unpreload_associations.empty? || !unused_preload_associations.empty?)
126
- Rails.logger.warn '' if @@rails_logger
127
- unused_preload_messages(path).each do |message|
128
- @@logger.info(message.join("\n")) if @@bullet_logger
129
- Rails.logger.warn(message.join("\n")) if @@rails_logger
130
- end
131
- unpreload_messages(path).each do |message|
132
- @@logger.info(message.join("\n")) if @@bullet_logger
133
- Rails.logger.warn(message.join("\n")) if @@rails_logger
134
- end
135
- call_stack_messages.each do |message|
136
- @@logger.info(message.join("\n")) if @@bullet_logger
137
- Rails.logger.warn(message.join("\n")) if @@rails_logger
138
- end
139
- @@logger_file.flush if @@bullet_logger
140
- end
141
- end
142
-
143
25
  def add_unpreload_associations(klazz, associations)
144
- # puts "add unpreload associations, #{klazz} => #{associations.inspect}"
145
26
  unpreload_associations[klazz] ||= []
146
27
  unpreload_associations[klazz] << associations
147
28
  unique(unpreload_associations[klazz])
148
29
  end
149
30
 
150
31
  def add_unused_preload_associations(klazz, associations)
151
- # puts "add unused preload associations, #{klazz} => #{associations.inspect}"
152
32
  unused_preload_associations[klazz] ||= []
153
33
  unused_preload_associations[klazz] << associations
154
34
  unique(unused_preload_associations[klazz])
155
35
  end
156
36
 
157
37
  def add_association(object, associations)
158
- # puts "add associations, #{object} => #{associations.inspect}"
159
38
  object_associations[object] ||= []
160
39
  object_associations[object] << associations
161
40
  unique(object_associations[object])
162
41
  end
163
42
 
164
- ###################################################
165
- # interface for active record
166
- ###################################################
167
43
  def add_call_object_associations(object, associations)
168
- # puts "add call object associations, #{object} => #{associations.inspect}"
169
44
  call_object_associations[object] ||= []
170
45
  call_object_associations[object] << associations
171
46
  unique(call_object_associations[object])
172
47
  end
173
48
 
174
49
  def add_possible_objects(objects)
175
- # puts "add possible objects, #{objects.inspect}"
176
50
  klazz= objects.first.class
177
51
  possible_objects[klazz] ||= []
178
52
  possible_objects[klazz] << objects
@@ -180,7 +54,6 @@ module Bullet
180
54
  end
181
55
 
182
56
  def add_impossible_object(object)
183
- # puts "add impossible object, #{object}"
184
57
  klazz = object.class
185
58
  impossible_objects[klazz] ||= []
186
59
  impossible_objects[klazz] << object
@@ -188,14 +61,12 @@ module Bullet
188
61
  end
189
62
 
190
63
  def add_klazz_associations(klazz, associations)
191
- # puts "define associations, #{klazz} => #{associations.inspect}"
192
64
  klazz_associations[klazz] ||= []
193
65
  klazz_associations[klazz] << associations
194
66
  unique(klazz_associations[klazz])
195
67
  end
196
68
 
197
69
  def add_eager_loadings(objects, associations)
198
- # puts "add eager loadings, #{objects.inspect} => #{associations.inspect}"
199
70
  objects = Array(objects)
200
71
  eager_loadings[objects] ||= []
201
72
  eager_loadings[objects] << associations
@@ -203,12 +74,10 @@ module Bullet
203
74
  end
204
75
 
205
76
  def define_association(klazz, associations)
206
- # puts "define association, #{klazz} => #{associations.inspect}"
207
77
  add_klazz_associations(klazz, associations)
208
78
  end
209
79
 
210
80
  def call_association(object, associations)
211
- # puts "call association, #{object} => #{associations.inspect}"
212
81
  add_call_object_associations(object, associations)
213
82
  if unpreload_associations?(object, associations)
214
83
  add_unpreload_associations(object.class, associations)
@@ -216,9 +85,6 @@ module Bullet
216
85
  end
217
86
  end
218
87
 
219
- ############################################
220
- # for rspec
221
- ############################################
222
88
  def check_unused_preload_associations
223
89
  object_associations.each do |object, association|
224
90
  related_objects = eager_loadings.select {|key, value| key.include?(object) and value == association}.collect(&:first).flatten
@@ -236,117 +102,122 @@ module Bullet
236
102
  !unpreload_associations.empty?
237
103
  end
238
104
 
239
-
240
105
  private
241
- def unpreload_associations?(object, associations)
242
- klazz = object.class
243
- (!possible_objects[klazz].nil? and possible_objects[klazz].include?(object)) and
244
- (impossible_objects[klazz].nil? or !impossible_objects[klazz].include?(object)) and
245
- (object_associations[object].nil? or !object_associations[object].include?(associations))
246
- end
106
+ def unpreload_associations?(object, associations)
107
+ klazz = object.class
108
+ (!possible_objects[klazz].nil? and possible_objects[klazz].include?(object)) and
109
+ (impossible_objects[klazz].nil? or !impossible_objects[klazz].include?(object)) and
110
+ (object_associations[object].nil? or !object_associations[object].include?(associations))
111
+ end
247
112
 
248
- def notification_response
249
- response = []
250
- if has_unused_preload_associations?
251
- response << unused_preload_messages.join("\n")
113
+ def notification_response
114
+ response = []
115
+ if has_unused_preload_associations?
116
+ response << unused_preload_messages.join("\n")
117
+ end
118
+ if has_unpreload_associations?
119
+ response << unpreload_messages.join("\n")
120
+ end
121
+ response
252
122
  end
253
- if has_unpreload_associations?
254
- response << unpreload_messages.join("\n")
123
+
124
+ def console_title
125
+ title = []
126
+ title << unused_preload_messages.first.first unless unused_preload_messages.empty?
127
+ title << unpreload_messages.first.first unless unpreload_messages.empty?
255
128
  end
256
- response
257
- end
258
129
 
259
- def unused_preload_messages(path = nil)
260
- messages = []
261
- unused_preload_associations.each do |klazz, associations|
262
- messages << [
263
- "Unused Eager Loading #{path ? "in #{path}" : 'detected'}",
264
- klazz_associations_str(klazz, associations),
265
- " Remove from your finder: #{associations_str(associations)}"
266
- ]
130
+ def log_messages(path = nil)
131
+ messages = []
132
+ messages << unused_preload_messages(path)
133
+ messages << unpreload_messages(path)
134
+ messages << call_stack_messages
267
135
  end
268
- messages
269
- end
270
136
 
271
- def unpreload_messages(path = nil)
272
- messages = []
273
- unpreload_associations.each do |klazz, associations|
274
- messages << [
275
- "N+1 Query #{path ? "in #{path}" : 'detected'}",
276
- klazz_associations_str(klazz, associations),
277
- " Add to your finder: #{associations_str(associations)}"
278
- ]
137
+ def unused_preload_messages(path = nil)
138
+ messages = []
139
+ unused_preload_associations.each do |klazz, associations|
140
+ messages << [
141
+ "Unused Eager Loading #{path ? "in #{path}" : 'detected'}",
142
+ klazz_associations_str(klazz, associations),
143
+ " Remove from your finder: #{associations_str(associations)}"
144
+ ]
145
+ end
146
+ messages
279
147
  end
280
- messages
281
- end
282
148
 
283
- def klazz_associations_str(klazz, associations)
284
- " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"
285
- end
149
+ def unpreload_messages(path = nil)
150
+ messages = []
151
+ unpreload_associations.each do |klazz, associations|
152
+ messages << [
153
+ "N+1 Query #{path ? "in #{path}" : 'detected'}",
154
+ klazz_associations_str(klazz, associations),
155
+ " Add to your finder: #{associations_str(associations)}"
156
+ ]
157
+ end
158
+ messages
159
+ end
286
160
 
287
- def associations_str(associations)
288
- ":include => #{associations.map{|a| a.to_sym unless a.is_a? Hash}.inspect}"
289
- end
161
+ def call_stack_messages
162
+ callers.inject([]) do |messages, c|
163
+ messages << ['N+1 Query method call stack', c.collect {|line| " #{line}"}].flatten
164
+ end
165
+ end
290
166
 
291
- def wrap_js_association(message)
292
- str = ''
293
- str << "<script type=\"text/javascript\">/*<![CDATA[*/"
294
- str << message
295
- str << "/*]]>*/</script>\n"
296
- end
167
+ def klazz_associations_str(klazz, associations)
168
+ " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"
169
+ end
297
170
 
298
- def call_stack_messages
299
- callers.inject([]) do |messages, c|
300
- messages << ['N+1 Query method call stack', c.collect {|line| " #{line}"}].flatten
171
+ def associations_str(associations)
172
+ ":include => #{associations.map{|a| a.to_sym unless a.is_a? Hash}.inspect}"
173
+ end
174
+
175
+ def unique(array)
176
+ array.flatten!
177
+ array.uniq!
301
178
  end
302
- end
303
-
304
- def unique(array)
305
- array.flatten!
306
- array.uniq!
307
- end
308
179
 
309
- def unpreload_associations
310
- @@unpreload_associations ||= {}
311
- end
180
+ def unpreload_associations
181
+ @@unpreload_associations ||= {}
182
+ end
312
183
 
313
- def unused_preload_associations
314
- @@unused_preload_associations ||= {}
315
- end
184
+ def unused_preload_associations
185
+ @@unused_preload_associations ||= {}
186
+ end
316
187
 
317
- def object_associations
318
- @@object_associations ||= {}
319
- end
188
+ def object_associations
189
+ @@object_associations ||= {}
190
+ end
320
191
 
321
- def call_object_associations
322
- @@call_object_associations ||= {}
323
- end
192
+ def call_object_associations
193
+ @@call_object_associations ||= {}
194
+ end
324
195
 
325
- def possible_objects
326
- @@possible_objects ||= {}
327
- end
196
+ def possible_objects
197
+ @@possible_objects ||= {}
198
+ end
328
199
 
329
- def impossible_objects
330
- @@impossible_objects ||= {}
331
- end
200
+ def impossible_objects
201
+ @@impossible_objects ||= {}
202
+ end
332
203
 
333
- def klazz_associations
334
- @@klazz_associations ||= {}
335
- end
204
+ def klazz_associations
205
+ @@klazz_associations ||= {}
206
+ end
336
207
 
337
- def eager_loadings
338
- @@eager_loadings ||= {}
339
- end
208
+ def eager_loadings
209
+ @@eager_loadings ||= {}
210
+ end
340
211
 
341
- VENDOR_ROOT = File.join(RAILS_ROOT, 'vendor')
342
- def caller_in_project
343
- callers << caller.select {|c| c =~ /#{RAILS_ROOT}/}.reject {|c| c =~ /#{VENDOR_ROOT}/}
344
- callers.uniq!
345
- end
212
+ VENDOR_ROOT = File.join(RAILS_ROOT, 'vendor')
213
+ def caller_in_project
214
+ callers << caller.select {|c| c =~ /#{RAILS_ROOT}/}.reject {|c| c =~ /#{VENDOR_ROOT}/}
215
+ callers.uniq!
216
+ end
346
217
 
347
- def callers
348
- @@callers ||= []
349
- end
218
+ def callers
219
+ @@callers ||= []
220
+ end
350
221
  end
351
222
  end
352
223
  end
@@ -1,8 +1,9 @@
1
1
  module Bullet
2
2
  class Counter
3
3
  class <<self
4
- def start_request
4
+ include Bullet::Notification
5
5
 
6
+ def start_request
6
7
  end
7
8
 
8
9
  def end_request
@@ -12,6 +13,26 @@ module Bullet
12
13
  def need_counter_caches?
13
14
  !klazz_associations.empty?
14
15
  end
16
+
17
+ def notification?
18
+ need_counter_caches?
19
+ end
20
+
21
+ def notification_response
22
+ response = []
23
+ if need_counter_caches?
24
+ response << counter_cache_messages.join("\n")
25
+ end
26
+ response
27
+ end
28
+
29
+ def console_title
30
+ title = ["Need Counter Cache"]
31
+ end
32
+
33
+ def log_messages(path = nil)
34
+ [counter_cache_messages(path)]
35
+ end
15
36
 
16
37
  def add_counter_cache(object, associations)
17
38
  klazz = object.class
@@ -21,9 +42,25 @@ module Bullet
21
42
  klazz_associations[klazz].uniq!
22
43
  end
23
44
 
24
- def klazz_associations
25
- @@klazz_associations ||= {}
26
- end
45
+ private
46
+ def counter_cache_messages(path = nil)
47
+ messages = []
48
+ klazz_associations.each do |klazz, associations|
49
+ messages << [
50
+ "Need Counter Cache",
51
+ " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"
52
+ ]
53
+ end
54
+ messages
55
+ end
56
+
57
+ def call_stack_messages
58
+ []
59
+ end
60
+
61
+ def klazz_associations
62
+ @@klazz_associations ||= {}
63
+ end
27
64
  end
28
65
  end
29
66
  end
@@ -0,0 +1,79 @@
1
+ module Bullet
2
+ class NotificationError < StandardError
3
+ end
4
+
5
+ module Notification
6
+ def notification?
7
+ end
8
+
9
+ def notification_response
10
+ end
11
+
12
+ def console_title
13
+ end
14
+
15
+ def log_message(path = nil)
16
+ end
17
+
18
+ def javascript_notification
19
+ str = ''
20
+ if Bullet.alert || Bullet.console
21
+ response = notification_response
22
+ end
23
+ if Bullet.alert
24
+ str << wrap_js_association("alert(#{response.join("\n").inspect});")
25
+ end
26
+ if Bullet.console
27
+ code = <<-CODE
28
+ if (typeof(console) !== 'undefined') {
29
+
30
+ if (console.groupCollapsed && console.groupEnd && console.log) {
31
+
32
+ console.groupCollapsed(#{console_title.join(', ').inspect});
33
+ console.log(#{response.join("\n").inspect});
34
+ console.log(#{call_stack_messages.join("\n").inspect});
35
+ console.groupEnd();
36
+
37
+ } else if (console.log) {
38
+
39
+ console.log(#{response.join("\n").inspect});
40
+ }
41
+ }
42
+ CODE
43
+ str << wrap_js_association(code)
44
+ end
45
+ str
46
+ end
47
+
48
+ def growl_notification
49
+ if Bullet.growl
50
+ response = notification_response
51
+ begin
52
+ growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, Bullet.growl_password)
53
+ growl.notify('Bullet Notification', 'Bullet Notification', response.join("\n"))
54
+ rescue
55
+ end
56
+ end
57
+ end
58
+
59
+ def log_notification(path)
60
+ if Bullet.bullet_logger || Bullet.rails_logger
61
+ Rails.logger.warn '' if Bullet.rails_logger
62
+ messages = log_messages(path)
63
+ messages.each do |message|
64
+ Bullet.logger.info(message.join("\n")) if Bullet.bullet_logger
65
+ Rails.logger.warn(message.join("\n")) if Bullet.rails_logger
66
+ end
67
+ Bullet.logger_file.flush if Bullet.bullet_logger
68
+ end
69
+ end
70
+
71
+ private
72
+ def wrap_js_association(message)
73
+ str = ''
74
+ str << "<script type=\"text/javascript\">/*<![CDATA[*/"
75
+ str << message
76
+ str << "/*]]>*/</script>\n"
77
+ end
78
+ end
79
+ end
data/lib/bulletware.rb CHANGED
@@ -6,21 +6,21 @@ class Bulletware
6
6
  def call(env)
7
7
  return @app.call(env) unless Bullet.enable?
8
8
 
9
- Bullet::Association.start_request
9
+ Bullet.start_request
10
10
  status, headers, response = @app.call(env)
11
11
  return [status, headers, response] if response.empty?
12
12
 
13
- if Bullet::Association.has_bad_assocations?
13
+ if Bullet.notification?
14
14
  if check_html?(headers, response)
15
- response_body = response.body << Bullet::Association.javascript_notification
15
+ response_body = response.body << Bullet.javascript_notification
16
16
  headers['Content-Length'] = response_body.length.to_s
17
17
  end
18
18
 
19
- Bullet::Association.growl_notification
20
- Bullet::Association.log_notificatioin(env['PATH_INFO'])
19
+ Bullet.growl_notification
20
+ Bullet.log_notification(env['PATH_INFO'])
21
21
  end
22
22
  response_body ||= response.body
23
- Bullet::Association.end_request
23
+ Bullet.end_request
24
24
  [status, headers, response_body]
25
25
  end
26
26
 
@@ -54,12 +54,19 @@ describe Bullet::Counter do
54
54
  Bullet::Counter.end_request
55
55
  end
56
56
 
57
- it "should need counter cache with count" do
57
+ it "should need counter cache with all cities" do
58
58
  Country.all.each do |country|
59
59
  country.cities.size
60
60
  end
61
61
  Bullet::Counter.should be_need_counter_caches
62
62
  end
63
+
64
+ it "should not need counter cache with part of cities" do
65
+ Country.all.each do |country|
66
+ country.cities(:conditions => ["name = ?", 'first']).size
67
+ end
68
+ Bullet::Counter.should_not be_need_counter_cache
69
+ end
63
70
  end
64
71
 
65
72
  describe Bullet::Counter do
@@ -115,7 +122,7 @@ describe Bullet::Counter do
115
122
  Bullet::Counter.end_request
116
123
  end
117
124
 
118
- it "should need counter cache with count" do
125
+ it "should not need counter cache" do
119
126
  Person.all.each do |person|
120
127
  person.pets.size
121
128
  end
data/spec/spec_helper.rb CHANGED
@@ -4,6 +4,7 @@ require 'active_record'
4
4
  require 'action_controller'
5
5
 
6
6
  RAILS_ROOT = File.expand_path(__FILE__).split('/')[0..-3].join('/') unless defined? RAILS_ROOT
7
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/notification'))
7
8
  require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/logger'))
8
9
  require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/active_record'))
9
10
  require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/association'))
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.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-09 00:00:00 -07:00
12
+ date: 2009-09-11 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -32,6 +32,7 @@ files:
32
32
  - lib/bullet/association.rb
33
33
  - lib/bullet/counter.rb
34
34
  - lib/bullet/logger.rb
35
+ - lib/bullet/notification.rb
35
36
  - lib/bulletware.rb
36
37
  - rails/init.rb
37
38
  - spec/bullet_association_spec.rb
@@ -66,6 +67,6 @@ signing_key:
66
67
  specification_version: 3
67
68
  summary: A plugin to kill N+1 queries and unused eager loading
68
69
  test_files:
69
- - spec/bullet_association_spec.rb
70
70
  - spec/bullet_counter_spec.rb
71
71
  - spec/spec_helper.rb
72
+ - spec/bullet_association_spec.rb