bullet 2.0.0.beta.2 → 2.0.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 (41) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/README.textile +38 -8
  3. data/README_for_rails2.textile +19 -3
  4. data/lib/bullet/action_controller2.rb +4 -4
  5. data/lib/bullet/active_record2.rb +16 -16
  6. data/lib/bullet/active_record3.rb +16 -25
  7. data/lib/bullet/detector/association.rb +135 -0
  8. data/lib/bullet/detector/base.rb +19 -0
  9. data/lib/bullet/detector/counter.rb +43 -0
  10. data/lib/bullet/detector/n_plus_one_query.rb +39 -0
  11. data/lib/bullet/detector/unused_eager_association.rb +39 -0
  12. data/lib/bullet/detector.rb +9 -0
  13. data/lib/bullet/notification/base.rb +57 -0
  14. data/lib/bullet/notification/counter_cache.rb +13 -0
  15. data/lib/bullet/notification/n_plus_one_query.rb +32 -0
  16. data/lib/bullet/notification/unused_eager_loading.rb +14 -0
  17. data/lib/bullet/notification.rb +4 -79
  18. data/lib/bullet/notification_collector.rb +25 -0
  19. data/lib/bullet/rack.rb +44 -0
  20. data/lib/bullet/registry/association.rb +16 -0
  21. data/lib/bullet/registry/base.rb +39 -0
  22. data/lib/bullet/registry/object.rb +15 -0
  23. data/lib/bullet/registry.rb +7 -0
  24. data/lib/bullet/version.rb +5 -0
  25. data/lib/bullet.rb +63 -42
  26. metadata +60 -42
  27. data/Rakefile +0 -34
  28. data/VERSION +0 -1
  29. data/bullet.gemspec +0 -67
  30. data/lib/bullet/association.rb +0 -294
  31. data/lib/bullet/counter.rb +0 -101
  32. data/lib/bullet/logger.rb +0 -9
  33. data/lib/bulletware.rb +0 -42
  34. data/rails/init.rb +0 -1
  35. data/spec/bullet/association_for_chris_spec.rb +0 -96
  36. data/spec/bullet/association_for_peschkaj_spec.rb +0 -86
  37. data/spec/bullet/association_spec.rb +0 -1043
  38. data/spec/bullet/counter_spec.rb +0 -136
  39. data/spec/spec.opts +0 -3
  40. data/spec/spec_helper.rb +0 -45
  41. data/tasks/bullet_tasks.rake +0 -9
@@ -0,0 +1,57 @@
1
+ module Bullet
2
+ module Notification
3
+ class Base
4
+ attr_accessor :notifier
5
+ attr_reader :base_class, :associations, :path
6
+
7
+ def initialize( base_class, associations, path = nil )
8
+ @base_class = base_class
9
+ @associations = associations.is_a?( Array ) ? associations : [ associations ]
10
+ @path = path
11
+ end
12
+
13
+ def title
14
+ end
15
+
16
+ def body
17
+ end
18
+
19
+ def body_with_caller
20
+ body
21
+ end
22
+
23
+ def standard_notice
24
+ @standard_notifice ||= title + "\n" + body
25
+ end
26
+
27
+ def full_notice
28
+ @full_notice ||= title + "\n" + body_with_caller
29
+ end
30
+
31
+ def notify_inline
32
+ self.notifier.inline_notify( self.full_notice )
33
+ end
34
+
35
+ def notify_out_of_channel
36
+ self.notifier.out_of_channel_notify( self.full_notice )
37
+ end
38
+
39
+ def eql?( other )
40
+ klazz_associations_str == other.klazz_associations_str
41
+ end
42
+
43
+ def hash
44
+ klazz_associations_str.hash
45
+ end
46
+
47
+ protected
48
+ def klazz_associations_str
49
+ " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
50
+ end
51
+
52
+ def associations_str
53
+ ":include => #{@associations.map{|a| a.to_s.to_sym unless a.is_a? Hash}.inspect}"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,13 @@
1
+ module Bullet
2
+ module Notification
3
+ class CounterCache < Base
4
+ def body
5
+ klazz_associations_str
6
+ end
7
+
8
+ def title
9
+ "Need Counter Cache"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module Bullet
2
+ module Notification
3
+ class NPlusOneQuery < Base
4
+ def initialize( callers, base_class, associations, path = nil )
5
+ super( base_class, associations, path )
6
+
7
+ @callers = callers
8
+ end
9
+
10
+ def body_with_caller
11
+ "#{body}\n#{call_stack_messages}"
12
+ end
13
+
14
+ def body
15
+ "#{klazz_associations_str}\n Add to your finder: #{associations_str}"
16
+ end
17
+
18
+ def title
19
+ "N+1 Query #{@path ? "in #{@path}" : 'detected'}"
20
+ end
21
+
22
+ protected
23
+ def call_stack_messages
24
+ @callers.collect do |c|
25
+ [ 'N+1 Query method call stack',
26
+ c.collect {|line| " #{line}"} ].flatten
27
+ end.join( "\n" )
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,14 @@
1
+ module Bullet
2
+ module Notification
3
+ class UnusedEagerLoading < Base
4
+ def body
5
+ "#{klazz_associations_str}\n Remove from your finder: #{associations_str}"
6
+ end
7
+
8
+ def title
9
+ "Unused Eager Loading #{@path ? "in #{@path}" : 'detected'}"
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -1,83 +1,8 @@
1
1
  module Bullet
2
- class NotificationError < StandardError
3
- end
4
-
5
2
  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
- unless response.blank?
24
- if Bullet.alert
25
- str << wrap_js_association("alert(#{response.join("\n").inspect});")
26
- end
27
- if Bullet.console
28
- code = <<-CODE
29
- if (typeof(console) !== 'undefined') {
30
-
31
- if (console.groupCollapsed && console.groupEnd && console.log) {
32
-
33
- console.groupCollapsed(#{console_title.join(', ').inspect});
34
- console.log(#{response.join("\n").inspect});
35
- console.log(#{call_stack_messages.join("\n").inspect});
36
- console.groupEnd();
37
-
38
- } else if (console.log) {
39
-
40
- console.log(#{response.join("\n").inspect});
41
- }
42
- }
43
- CODE
44
- str << wrap_js_association(code)
45
- end
46
- end
47
- str
48
- end
49
-
50
- def growl_notification
51
- if Bullet.growl
52
- response = notification_response
53
- unless response.blank?
54
- begin
55
- growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, Bullet.growl_password)
56
- growl.notify('Bullet Notification', 'Bullet Notification', response.join("\n"))
57
- rescue
58
- end
59
- end
60
- end
61
- end
62
-
63
- def log_notification(path)
64
- if Bullet.bullet_logger || Bullet.rails_logger
65
- Rails.logger.warn '' if Bullet.rails_logger
66
- messages = log_messages(path)
67
- messages.each do |message|
68
- Bullet.logger.info(message.join("\n")) if Bullet.bullet_logger
69
- Rails.logger.warn(message.join("\n")) if Bullet.rails_logger
70
- end
71
- Bullet.logger_file.flush if Bullet.bullet_logger
72
- end
73
- end
74
-
75
- private
76
- def wrap_js_association(message)
77
- str = ''
78
- str << "<script type=\"text/javascript\">/*<![CDATA[*/"
79
- str << message
80
- str << "/*]]>*/</script>\n"
81
- end
3
+ autoload :Base, 'bullet/notification/base'
4
+ autoload :UnusedEagerLoading, 'bullet/notification/unused_eager_loading'
5
+ autoload :NPlusOneQuery, 'bullet/notification/n_plus_one_query'
6
+ autoload :CounterCache, 'bullet/notification/counter_cache'
82
7
  end
83
8
  end
@@ -0,0 +1,25 @@
1
+ require 'set'
2
+
3
+ module Bullet
4
+ class NotificationCollector
5
+ attr_reader :collection
6
+
7
+ def initialize
8
+ reset
9
+ end
10
+
11
+ def reset
12
+ @collection = Set.new
13
+ end
14
+
15
+ def add( value )
16
+ @collection << value
17
+ end
18
+
19
+ def notifications_present?
20
+ !@collection.empty?
21
+ end
22
+
23
+ end
24
+ end
25
+
@@ -0,0 +1,44 @@
1
+ module Bullet
2
+ class Rack
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ return @app.call(env) unless Bullet.enable?
9
+
10
+ Bullet.start_request
11
+ status, headers, response = @app.call(env)
12
+ return [status, headers, response] if empty?(response)
13
+
14
+ if Bullet.notification?
15
+ if status == 200 and !response.body.frozen? and check_html?(headers, response)
16
+ response_body = response.body << Bullet.gather_inline_notifications
17
+ headers['Content-Length'] = response_body.length.to_s
18
+ end
19
+ Bullet.perform_out_of_channel_notifications
20
+ end
21
+ response_body ||= response.body
22
+ Bullet.end_request
23
+ no_browser_cache(headers) if Bullet.disable_browser_cache
24
+ [status, headers, [response_body]]
25
+ end
26
+
27
+ # fix issue if response's body is a Proc
28
+ def empty?(response)
29
+ # response may be ["Not Found"], ["Move Permanently"], etc.
30
+ (response.is_a?(Array) && response.size <= 1) ||
31
+ !response.body.is_a?(String) || response.body.empty?
32
+ end
33
+
34
+ def check_html?(headers, response)
35
+ headers['Content-Type'] and headers['Content-Type'].include? 'text/html' and response.body =~ %r{<html.*</html>}m
36
+ end
37
+
38
+ def no_browser_cache(headers)
39
+ headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
40
+ headers["Pragma"] = "no-cache"
41
+ headers["Expires"] = "Wed, 09 Sep 2009 09:09:09 GMT"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,16 @@
1
+ module Bullet
2
+ module Registry
3
+ class Association < Base
4
+ def merge( base, associations )
5
+ @registry.merge!( { base => associations } )
6
+ unique( @registry[base] )
7
+ end
8
+
9
+ def similarly_associated( base, associations )
10
+ @registry.select do |key, value|
11
+ key.include?( base ) and value == associations
12
+ end.collect( &:first ).flatten!
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ module Bullet
2
+ module Registry
3
+ class Base
4
+ attr_reader :registry
5
+
6
+ def initialize
7
+ @registry = {}
8
+ end
9
+
10
+ def [](key)
11
+ @registry[key]
12
+ end
13
+
14
+ def each( &block )
15
+ @registry.each( &block )
16
+ end
17
+
18
+ def delete( base )
19
+ @registry.delete( base )
20
+ end
21
+
22
+ def select( *args, &block )
23
+ @registry.select( *args, &block )
24
+ end
25
+
26
+ def add( key, value )
27
+ @registry[key] ||= []
28
+ @registry[key] << value
29
+ unique( @registry[key] )
30
+ end
31
+
32
+ private
33
+ def unique( array )
34
+ array.flatten!
35
+ array.uniq!
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Bullet
2
+ module Registry
3
+ class Object < Base
4
+ def add( object_or_objects )
5
+ klazz = object_or_objects.is_a?( Array ) ? object_or_objects.first.class :
6
+ object_or_objects.class
7
+ super( klazz, object_or_objects )
8
+ end
9
+
10
+ def contains?( object )
11
+ @registry[object.class] and @registry[object.class].include?( object )
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Bullet
2
+ module Registry
3
+ autoload :Base, 'bullet/registry/base'
4
+ autoload :Object, 'bullet/registry/object'
5
+ autoload :Association, 'bullet/registry/association'
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ module Bullet
3
+ VERSION = "2.0.1"
4
+ end
5
+
data/lib/bullet.rb CHANGED
@@ -1,31 +1,47 @@
1
- require 'bulletware'
1
+ require 'set'
2
+ require 'uniform_notifier'
2
3
 
3
4
  module Bullet
5
+ class NotificationError < StandardError; end
6
+
4
7
  if Rails.version =~ /^3.0/
5
8
  autoload :ActiveRecord, 'bullet/active_record3'
6
9
  else
7
10
  autoload :ActiveRecord, 'bullet/active_record2'
8
11
  autoload :ActionController, 'bullet/action_controller2'
9
12
  end
10
- autoload :Association, 'bullet/association'
11
- autoload :Counter, 'bullet/counter'
13
+ autoload :Rack, 'bullet/rack'
12
14
  autoload :BulletLogger, 'bullet/logger'
13
15
  autoload :Notification, 'bullet/notification'
16
+ autoload :Detector, 'bullet/detector'
17
+ autoload :Registry, 'bullet/registry'
18
+ autoload :NotificationCollector, 'bullet/notification_collector'
19
+
20
+ if defined? Rails::Railtie
21
+ # compatible with rails 3.0.0.beta4
22
+ class BulletRailtie < Rails::Railtie
23
+ initializer "bullet.configure_rails_initialization" do |app|
24
+ app.middleware.use Bullet::Rack
25
+ end
26
+ end
27
+ end
14
28
 
15
29
  class <<self
16
- attr_accessor :enable, :alert, :console, :growl, :growl_password, :rails_logger, :bullet_logger, :logger, :logger_file, :disable_browser_cache
30
+ attr_accessor :enable, :disable_browser_cache
31
+ attr_reader :notification_collector
32
+
33
+ delegate :alert=, :console=, :growl=, :rails_logger=, :xmpp=, :to => UniformNotifier
34
+
35
+ DETECTORS = [ Bullet::Detector::NPlusOneQuery,
36
+ Bullet::Detector::UnusedEagerAssociation,
37
+ Bullet::Detector::Counter ]
17
38
 
18
39
  def enable=(enable)
19
40
  @enable = enable
20
- if enable?
41
+ if enable?
21
42
  Bullet::ActiveRecord.enable
22
- if Rails.version =~ /^3.0/
23
- require 'action_controller/metal'
24
- ::ActionController::Metal.middleware_stack.use Bulletware
25
- elsif Rails.version =~/^2.3/
43
+ if Rails.version =~ /^2./
26
44
  Bullet::ActionController.enable
27
- require 'action_controller/dispatcher'
28
- ::ActionController::Dispatcher.middleware.use Bulletware
29
45
  end
30
46
  end
31
47
  end
@@ -34,54 +50,59 @@ module Bullet
34
50
  @enable == true
35
51
  end
36
52
 
37
- def growl=(growl)
38
- if growl
39
- begin
40
- require 'ruby-growl'
41
- growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @growl_password)
42
- growl.notify('Bullet Notification', 'Bullet Notification', 'Bullet Growl notifications have been turned on')
43
- rescue MissingSourceFile
44
- raise NotificationError.new('You must install the ruby-growl gem to use Growl notifications: `sudo gem install ruby-growl`')
45
- end
46
- end
47
- @growl = growl
48
- end
49
-
50
- def bullet_logger=(bullet_logger)
51
- if @bullet_logger = bullet_logger
52
- @logger_file = File.open(Bullet::BulletLogger::LOG_FILE, 'a+')
53
- @logger = Bullet::BulletLogger.new(@logger_file)
53
+ def bullet_logger=(active)
54
+ if active
55
+ bullet_log_file = File.open( 'log/bullet.log', 'a+' )
56
+ bullet_log_file.sync
57
+ UniformNotifier.customized_logger = bullet_log_file
54
58
  end
55
59
  end
56
60
 
57
- BULLETS = [Bullet::Association, Bullet::Counter]
58
-
59
61
  def start_request
60
- BULLETS.each {|bullet| bullet.start_request}
62
+ notification_collector.reset
63
+ DETECTORS.each {|bullet| bullet.start_request}
61
64
  end
62
65
 
63
66
  def end_request
64
- BULLETS.each {|bullet| bullet.end_request}
67
+ DETECTORS.each {|bullet| bullet.end_request}
65
68
  end
66
-
69
+
67
70
  def clear
68
- BULLETS.each {|bullet| bullet.clear}
71
+ DETECTORS.each {|bullet| bullet.clear}
69
72
  end
70
73
 
71
- def notification?
72
- BULLETS.any? {|bullet| bullet.notification?}
74
+ def notification_collector
75
+ @notification_collector ||= Bullet::NotificationCollector.new
73
76
  end
74
77
 
75
- def javascript_notification
76
- BULLETS.collect {|bullet| bullet.javascript_notification if bullet.notification?}.join("\n")
78
+ def notification?
79
+ Bullet::Detector::UnusedEagerAssociation.check_unused_preload_associations
80
+ notification_collector.notifications_present?
77
81
  end
78
82
 
79
- def growl_notification
80
- BULLETS.each {|bullet| bullet.growl_notification if bullet.notification?}
83
+ def gather_inline_notifications
84
+ responses = []
85
+ for_each_active_notifier_with_notification do |notification|
86
+ responses << notification.notify_inline
87
+ end
88
+ responses.join( "\n" )
81
89
  end
82
90
 
83
- def log_notification(path)
84
- BULLETS.each {|bullet| bullet.log_notification(path) if bullet.notification?}
91
+ def perform_out_of_channel_notifications
92
+ for_each_active_notifier_with_notification do |notification|
93
+ notification.notify_out_of_channel
94
+ end
85
95
  end
96
+
97
+ private
98
+ def for_each_active_notifier_with_notification
99
+ UniformNotifier.active_notifiers.each do |notifier|
100
+ notification_collector.collection.each do |notification|
101
+ notification.notifier = notifier
102
+ yield notification
103
+ end
104
+ end
105
+ end
86
106
  end
107
+
87
108
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ hash: 13
5
+ prerelease: false
5
6
  segments:
6
7
  - 2
7
8
  - 0
8
- - 0
9
- - beta
10
- - 2
11
- version: 2.0.0.beta.2
9
+ - 1
10
+ version: 2.0.1
12
11
  platform: ruby
13
12
  authors:
14
13
  - Richard Huang
@@ -16,78 +15,97 @@ autorequire:
16
15
  bindir: bin
17
16
  cert_chain: []
18
17
 
19
- date: 2010-03-07 00:00:00 +08:00
18
+ date: 2010-11-19 00:00:00 +08:00
20
19
  default_executable:
21
- dependencies: []
22
-
23
- 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.
24
- email: flyerhzm@gmail.com
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ version_requirements: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ hash: 23
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 0
32
+ version: 1.0.0
33
+ requirement: *id001
34
+ name: uniform_notifier
35
+ prerelease: false
36
+ type: :runtime
37
+ description: A rails plugin to kill N+1 queries and unused eager loading.
38
+ email:
39
+ - flyerhzm@gmail.com
25
40
  executables: []
26
41
 
27
42
  extensions: []
28
43
 
29
44
  extra_rdoc_files:
30
- - README.textile
31
- - README_for_rails2.textile
32
- files:
33
45
  - MIT-LICENSE
34
46
  - README.textile
35
47
  - README_for_rails2.textile
36
- - Rakefile
37
- - VERSION
38
- - bullet.gemspec
39
- - lib/bullet.rb
48
+ files:
40
49
  - lib/bullet/action_controller2.rb
41
50
  - lib/bullet/active_record2.rb
42
51
  - lib/bullet/active_record3.rb
43
- - lib/bullet/association.rb
44
- - lib/bullet/counter.rb
45
- - lib/bullet/logger.rb
52
+ - lib/bullet/detector/association.rb
53
+ - lib/bullet/detector/base.rb
54
+ - lib/bullet/detector/counter.rb
55
+ - lib/bullet/detector/n_plus_one_query.rb
56
+ - lib/bullet/detector/unused_eager_association.rb
57
+ - lib/bullet/detector.rb
58
+ - lib/bullet/notification/base.rb
59
+ - lib/bullet/notification/counter_cache.rb
60
+ - lib/bullet/notification/n_plus_one_query.rb
61
+ - lib/bullet/notification/unused_eager_loading.rb
46
62
  - lib/bullet/notification.rb
47
- - lib/bulletware.rb
48
- - rails/init.rb
49
- - spec/bullet/association_for_chris_spec.rb
50
- - spec/bullet/association_for_peschkaj_spec.rb
51
- - spec/bullet/association_spec.rb
52
- - spec/bullet/counter_spec.rb
53
- - spec/spec.opts
54
- - spec/spec_helper.rb
55
- - tasks/bullet_tasks.rake
63
+ - lib/bullet/notification_collector.rb
64
+ - lib/bullet/rack.rb
65
+ - lib/bullet/registry/association.rb
66
+ - lib/bullet/registry/base.rb
67
+ - lib/bullet/registry/object.rb
68
+ - lib/bullet/registry.rb
69
+ - lib/bullet/version.rb
70
+ - lib/bullet.rb
71
+ - MIT-LICENSE
72
+ - README.textile
73
+ - README_for_rails2.textile
56
74
  has_rdoc: true
57
75
  homepage: http://github.com/flyerhzm/bullet
58
76
  licenses: []
59
77
 
60
78
  post_install_message:
61
- rdoc_options:
62
- - --charset=UTF-8
79
+ rdoc_options: []
80
+
63
81
  require_paths:
64
82
  - lib
65
83
  required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
66
85
  requirements:
67
86
  - - ">="
68
87
  - !ruby/object:Gem::Version
88
+ hash: 3
69
89
  segments:
70
90
  - 0
71
91
  version: "0"
72
92
  required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
73
94
  requirements:
74
- - - ">"
95
+ - - ">="
75
96
  - !ruby/object:Gem::Version
97
+ hash: 23
76
98
  segments:
77
99
  - 1
78
100
  - 3
79
- - 1
80
- version: 1.3.1
101
+ - 6
102
+ version: 1.3.6
81
103
  requirements: []
82
104
 
83
105
  rubyforge_project:
84
- rubygems_version: 1.3.6
106
+ rubygems_version: 1.3.7
85
107
  signing_key:
86
108
  specification_version: 3
87
- summary: A plugin to kill N+1 queries and unused eager loading
88
- test_files:
89
- - spec/spec_helper.rb
90
- - spec/bullet/counter_spec.rb
91
- - spec/bullet/association_spec.rb
92
- - spec/bullet/association_for_chris_spec.rb
93
- - spec/bullet/association_for_peschkaj_spec.rb
109
+ summary: A rails plugin to kill N+1 queries and unused eager loading.
110
+ test_files: []
111
+