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.
- data/.rspec +1 -0
- data/Hacking.textile +100 -0
- data/README.textile +36 -5
- data/README_for_rails2.textile +17 -0
- data/Rakefile +33 -16
- data/VERSION +1 -1
- data/autotest/discover.rb +1 -0
- data/bullet.gemspec +32 -9
- data/lib/bullet.rb +69 -38
- data/lib/bullet/action_controller2.rb +4 -4
- data/lib/bullet/active_record2.rb +16 -16
- data/lib/bullet/active_record3.rb +16 -25
- data/lib/bullet/detector.rb +9 -0
- data/lib/bullet/detector/association.rb +135 -0
- data/lib/bullet/detector/base.rb +19 -0
- data/lib/bullet/detector/counter.rb +43 -0
- data/lib/bullet/detector/n_plus_one_query.rb +39 -0
- data/lib/bullet/detector/unused_eager_association.rb +39 -0
- data/lib/bullet/notification.rb +4 -79
- data/lib/bullet/notification/base.rb +59 -0
- data/lib/bullet/notification/counter_cache.rb +13 -0
- data/lib/bullet/notification/n_plus_one_query.rb +32 -0
- data/lib/bullet/notification/unused_eager_loading.rb +14 -0
- data/lib/bullet/notification_collector.rb +25 -0
- data/lib/bullet/presenter.rb +13 -0
- data/lib/bullet/presenter/base.rb +9 -0
- data/lib/bullet/presenter/bullet_logger.rb +28 -0
- data/lib/bullet/presenter/growl.rb +40 -0
- data/lib/bullet/presenter/javascript_alert.rb +15 -0
- data/lib/bullet/presenter/javascript_console.rb +28 -0
- data/lib/bullet/presenter/javascript_helpers.rb +13 -0
- data/lib/bullet/presenter/rails_logger.rb +15 -0
- data/lib/bullet/presenter/xmpp.rb +56 -0
- data/lib/bullet/rack.rb +42 -0
- data/lib/bullet/registry.rb +7 -0
- data/lib/bullet/registry/association.rb +16 -0
- data/lib/bullet/registry/base.rb +39 -0
- data/lib/bullet/registry/object.rb +15 -0
- data/spec/bullet/association_for_chris_spec.rb +6 -6
- data/spec/bullet/association_for_peschkaj_spec.rb +6 -6
- data/spec/bullet/association_spec.rb +118 -262
- data/spec/bullet/counter_spec.rb +10 -10
- data/spec/spec_helper.rb +51 -17
- metadata +32 -9
- data/lib/bullet/association.rb +0 -294
- data/lib/bullet/counter.rb +0 -101
- data/lib/bullet/logger.rb +0 -9
- data/lib/bulletware.rb +0 -42
- 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
|
data/lib/bullet/notification.rb
CHANGED
@@ -1,83 +1,8 @@
|
|
1
1
|
module Bullet
|
2
|
-
class NotificationError < StandardError
|
3
|
-
end
|
4
|
-
|
5
2
|
module Notification
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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,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,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,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
|