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

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 (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