bullet 2.0.0.beta.2 → 2.0.0.beta.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.rspec +1 -0
  2. data/Hacking.textile +100 -0
  3. data/README.textile +36 -5
  4. data/README_for_rails2.textile +17 -0
  5. data/Rakefile +33 -16
  6. data/VERSION +1 -1
  7. data/autotest/discover.rb +1 -0
  8. data/bullet.gemspec +32 -9
  9. data/lib/bullet.rb +69 -38
  10. data/lib/bullet/action_controller2.rb +4 -4
  11. data/lib/bullet/active_record2.rb +16 -16
  12. data/lib/bullet/active_record3.rb +16 -25
  13. data/lib/bullet/detector.rb +9 -0
  14. data/lib/bullet/detector/association.rb +135 -0
  15. data/lib/bullet/detector/base.rb +19 -0
  16. data/lib/bullet/detector/counter.rb +43 -0
  17. data/lib/bullet/detector/n_plus_one_query.rb +39 -0
  18. data/lib/bullet/detector/unused_eager_association.rb +39 -0
  19. data/lib/bullet/notification.rb +4 -79
  20. data/lib/bullet/notification/base.rb +59 -0
  21. data/lib/bullet/notification/counter_cache.rb +13 -0
  22. data/lib/bullet/notification/n_plus_one_query.rb +32 -0
  23. data/lib/bullet/notification/unused_eager_loading.rb +14 -0
  24. data/lib/bullet/notification_collector.rb +25 -0
  25. data/lib/bullet/presenter.rb +13 -0
  26. data/lib/bullet/presenter/base.rb +9 -0
  27. data/lib/bullet/presenter/bullet_logger.rb +28 -0
  28. data/lib/bullet/presenter/growl.rb +40 -0
  29. data/lib/bullet/presenter/javascript_alert.rb +15 -0
  30. data/lib/bullet/presenter/javascript_console.rb +28 -0
  31. data/lib/bullet/presenter/javascript_helpers.rb +13 -0
  32. data/lib/bullet/presenter/rails_logger.rb +15 -0
  33. data/lib/bullet/presenter/xmpp.rb +56 -0
  34. data/lib/bullet/rack.rb +42 -0
  35. data/lib/bullet/registry.rb +7 -0
  36. data/lib/bullet/registry/association.rb +16 -0
  37. data/lib/bullet/registry/base.rb +39 -0
  38. data/lib/bullet/registry/object.rb +15 -0
  39. data/spec/bullet/association_for_chris_spec.rb +6 -6
  40. data/spec/bullet/association_for_peschkaj_spec.rb +6 -6
  41. data/spec/bullet/association_spec.rb +118 -262
  42. data/spec/bullet/counter_spec.rb +10 -10
  43. data/spec/spec_helper.rb +51 -17
  44. metadata +32 -9
  45. data/lib/bullet/association.rb +0 -294
  46. data/lib/bullet/counter.rb +0 -101
  47. data/lib/bullet/logger.rb +0 -9
  48. data/lib/bulletware.rb +0 -42
  49. data/spec/spec.opts +0 -3
@@ -0,0 +1,39 @@
1
+ module Bullet
2
+ module Detector
3
+ class UnusedEagerAssociation < Association
4
+ # check if there are unused preload associations.
5
+ # for each object => association
6
+ # get related_objects from eager_loadings associated with object and associations
7
+ # get call_object_association from associations of call_object_associations whose object is in related_objects
8
+ # if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
9
+ def self.check_unused_preload_associations
10
+ @@checked = true
11
+ object_associations.each do |object, association|
12
+ object_association_diff = diff_object_association object, association
13
+ next if object_association_diff.empty?
14
+
15
+ create_notification object.class, object_association_diff
16
+ end
17
+ end
18
+
19
+ protected
20
+ def self.create_notification(klazz, associations)
21
+ notice = Bullet::Notification::UnusedEagerLoading.new( klazz, associations )
22
+ Bullet.notification_collector.add( notice )
23
+ end
24
+
25
+ def self.call_object_association( object, association )
26
+ eager_loadings.similarly_associated( object, association ).
27
+ collect { |related_object| call_object_associations[related_object] }.
28
+ compact.
29
+ flatten.
30
+ uniq
31
+ end
32
+
33
+ def self.diff_object_association( object, association )
34
+ potential_objects = association - call_object_association( object, association )
35
+ potential_objects.reject {|a| a.is_a?( Hash ) }
36
+ end
37
+ end
38
+ end
39
+ 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,59 @@
1
+ module Bullet
2
+ module Notification
3
+ class Base
4
+ attr_accessor :presenter
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 present_inline
32
+ return unless self.presenter.respond_to? :inline
33
+ self.presenter.send( :inline, self )
34
+ end
35
+
36
+ def present_out_of_channel
37
+ return unless self.presenter.respond_to? :out_of_channel
38
+ self.presenter.send( :out_of_channel, self )
39
+ end
40
+
41
+ def eql?( other )
42
+ klazz_associations_str == other.klazz_associations_str
43
+ end
44
+
45
+ def hash
46
+ klazz_associations_str.hash
47
+ end
48
+
49
+ protected
50
+ def klazz_associations_str
51
+ " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
52
+ end
53
+
54
+ def associations_str
55
+ ":include => #{@associations.map{|a| a.to_s.to_sym unless a.is_a? Hash}.inspect}"
56
+ end
57
+ end
58
+ end
59
+ 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
@@ -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,13 @@
1
+ module Bullet
2
+ module Presenter
3
+ autoload :Base, 'bullet/presenter/base'
4
+ autoload :JavascriptAlert, 'bullet/presenter/javascript_alert'
5
+ autoload :JavascriptConsole, 'bullet/presenter/javascript_console'
6
+ autoload :Growl, 'bullet/presenter/growl'
7
+ autoload :Xmpp, 'bullet/presenter/xmpp'
8
+ autoload :RailsLogger, 'bullet/presenter/rails_logger'
9
+ autoload :BulletLogger, 'bullet/presenter/bullet_logger'
10
+
11
+ autoload :JavascriptHelpers, 'bullet/presenter/javascript_helpers'
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Bullet
2
+ module Presenter
3
+ class Base
4
+ def self.active?
5
+ false
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module Bullet
2
+ module Presenter
3
+ class BulletLogger < Base
4
+ @logger_file = nil
5
+ @logger = nil
6
+
7
+ def self.active?
8
+ @logger
9
+ end
10
+
11
+ def self.out_of_channel( notice )
12
+ return unless active?
13
+ @logger.info notice.full_notice
14
+ @logger_file.flush
15
+ end
16
+
17
+ def self.setup
18
+ @logger_file = File.open( Rails.root.join('log/bullet.log'), 'a+' )
19
+ @logger = Logger.new( @logger_file )
20
+
21
+ def @logger.format_message( severity, timestamp, progname, msg )
22
+ "#{timestamp.to_formatted_s(:db)}[#{severity}] #{msg}\n"
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ module Bullet
2
+ module Presenter
3
+ class Growl < Base
4
+ @growl = nil
5
+
6
+ def self.active?
7
+ @growl
8
+ end
9
+
10
+ def self.out_of_channel( notice )
11
+ return unless active?
12
+ notify( notice.standard_notice )
13
+ end
14
+
15
+ def self.setup_connection( password )
16
+ require 'ruby-growl'
17
+ @password = password
18
+ @growl = connect
19
+
20
+ notify 'Bullet Growl notifications have been turned on'
21
+ rescue MissingSourceFile
22
+ @growl = nil
23
+ raise NotificationError.new( 'You must install the ruby-growl gem to use Growl notifications: `sudo gem install ruby-growl`' )
24
+ end
25
+
26
+ private
27
+ def self.connect
28
+ ::Growl.new 'localhost',
29
+ 'ruby-growl',
30
+ [ 'Bullet Notification' ],
31
+ nil,
32
+ @password
33
+ end
34
+
35
+ def self.notify( message )
36
+ @growl.notify( 'Bullet Notification', 'Bullet Notification', message )
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module Bullet
2
+ module Presenter
3
+ class JavascriptAlert < Base
4
+ def self.active?
5
+ Bullet.alert
6
+ end
7
+
8
+ def self.inline( notice )
9
+ return '' unless self.active?
10
+
11
+ JavascriptHelpers::wrap_js_association "alert( #{notice.standard_notice.inspect} ); "
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,28 @@
1
+ module Bullet
2
+ module Presenter
3
+ class JavascriptConsole < Base
4
+ def self.active?
5
+ Bullet.console
6
+ end
7
+
8
+ def self.inline( notice )
9
+ return '' unless active?
10
+
11
+ code = <<-CODE
12
+ if (typeof(console) !== 'undefined') {
13
+ if (console.groupCollapsed && console.groupEnd && console.log) {
14
+ console.groupCollapsed(#{notice.title.inspect});
15
+ console.log(#{notice.body_with_caller.inspect});
16
+ console.groupEnd();
17
+
18
+ } else if (console.log) {
19
+ console.log(#{notice.full_notice.inspect});
20
+ }
21
+ }
22
+ CODE
23
+
24
+ JavascriptHelpers::wrap_js_association code
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ module Bullet
2
+ module Presenter
3
+ module JavascriptHelpers
4
+ def self.wrap_js_association( message )
5
+ %{
6
+ <script type="text/javascript">/*<![CDATA[*/
7
+ #{message}
8
+ /*]]>*/</script>
9
+ }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Bullet
2
+ module Presenter
3
+ class RailsLogger < Base
4
+ def self.active?
5
+ Bullet.rails_logger
6
+ end
7
+
8
+ def self.out_of_channel( notice )
9
+ return unless active?
10
+ Rails.logger.warn ''
11
+ Rails.logger.warn notice.full_notice
12
+ end
13
+ end
14
+ end
15
+ end