bullet 5.7.0 → 6.0.0
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.
- checksums.yaml +5 -5
- data/.travis.yml +2 -0
- data/CHANGELOG.md +27 -1
- data/Gemfile.mongoid-7.0 +15 -0
- data/Gemfile.rails-4.0 +1 -1
- data/Gemfile.rails-4.1 +1 -1
- data/Gemfile.rails-4.2 +1 -1
- data/Gemfile.rails-5.0 +1 -1
- data/Gemfile.rails-5.1 +1 -1
- data/Gemfile.rails-5.2 +2 -2
- data/Gemfile.rails-6.0 +15 -0
- data/Guardfile +1 -1
- data/README.md +10 -5
- data/Rakefile +5 -5
- data/bullet.gemspec +12 -6
- data/lib/bullet/active_record4.rb +10 -14
- data/lib/bullet/active_record41.rb +8 -14
- data/lib/bullet/active_record42.rb +16 -22
- data/lib/bullet/active_record5.rb +69 -49
- data/lib/bullet/active_record52.rb +40 -40
- data/lib/bullet/active_record60.rb +245 -0
- data/lib/bullet/bullet_xhr.js +58 -0
- data/lib/bullet/dependency.rb +19 -5
- data/lib/bullet/detector/association.rb +18 -16
- data/lib/bullet/detector/base.rb +2 -0
- data/lib/bullet/detector/counter_cache.rb +13 -10
- data/lib/bullet/detector/n_plus_one_query.rb +21 -19
- data/lib/bullet/detector/unused_eager_loading.rb +27 -22
- data/lib/bullet/detector.rb +2 -0
- data/lib/bullet/ext/object.rb +9 -5
- data/lib/bullet/ext/string.rb +3 -1
- data/lib/bullet/mongoid4x.rb +7 -2
- data/lib/bullet/mongoid5x.rb +7 -2
- data/lib/bullet/mongoid6x.rb +11 -6
- data/lib/bullet/mongoid7x.rb +61 -0
- data/lib/bullet/notification/base.rb +21 -15
- data/lib/bullet/notification/counter_cache.rb +2 -0
- data/lib/bullet/notification/n_plus_one_query.rb +6 -4
- data/lib/bullet/notification/unused_eager_loading.rb +6 -4
- data/lib/bullet/notification.rb +2 -0
- data/lib/bullet/notification_collector.rb +2 -1
- data/lib/bullet/rack.rb +52 -31
- data/lib/bullet/registry/association.rb +2 -0
- data/lib/bullet/registry/base.rb +2 -0
- data/lib/bullet/registry/object.rb +2 -0
- data/lib/bullet/registry.rb +2 -0
- data/lib/bullet/stack_trace_filter.rb +57 -21
- data/lib/bullet/version.rb +2 -1
- data/lib/bullet.rb +48 -31
- data/lib/generators/bullet/install_generator.rb +5 -3
- data/perf/benchmark.rb +9 -7
- data/rails/init.rb +2 -0
- data/spec/bullet/detector/association_spec.rb +2 -0
- data/spec/bullet/detector/base_spec.rb +2 -0
- data/spec/bullet/detector/counter_cache_spec.rb +2 -0
- data/spec/bullet/detector/n_plus_one_query_spec.rb +33 -8
- data/spec/bullet/detector/unused_eager_loading_spec.rb +2 -0
- data/spec/bullet/ext/object_spec.rb +12 -5
- data/spec/bullet/ext/string_spec.rb +2 -0
- data/spec/bullet/notification/base_spec.rb +8 -6
- data/spec/bullet/notification/counter_cache_spec.rb +3 -1
- data/spec/bullet/notification/n_plus_one_query_spec.rb +3 -1
- data/spec/bullet/notification/unused_eager_loading_spec.rb +3 -1
- data/spec/bullet/notification_collector_spec.rb +2 -0
- data/spec/bullet/rack_spec.rb +22 -12
- data/spec/bullet/registry/association_spec.rb +4 -2
- data/spec/bullet/registry/base_spec.rb +5 -3
- data/spec/bullet/registry/object_spec.rb +2 -0
- data/spec/bullet_spec.rb +9 -7
- data/spec/integration/active_record/association_spec.rb +70 -19
- data/spec/integration/counter_cache_spec.rb +5 -3
- data/spec/integration/mongoid/association_spec.rb +2 -0
- data/spec/models/address.rb +2 -0
- data/spec/models/author.rb +2 -0
- data/spec/models/base_user.rb +2 -0
- data/spec/models/category.rb +2 -0
- data/spec/models/city.rb +2 -0
- data/spec/models/client.rb +4 -0
- data/spec/models/comment.rb +2 -0
- data/spec/models/company.rb +2 -0
- data/spec/models/country.rb +2 -0
- data/spec/models/document.rb +2 -0
- data/spec/models/entry.rb +2 -0
- data/spec/models/firm.rb +3 -0
- data/spec/models/folder.rb +2 -0
- data/spec/models/group.rb +4 -0
- data/spec/models/mongoid/address.rb +3 -1
- data/spec/models/mongoid/category.rb +4 -2
- data/spec/models/mongoid/comment.rb +3 -1
- data/spec/models/mongoid/company.rb +3 -1
- data/spec/models/mongoid/entry.rb +3 -1
- data/spec/models/mongoid/post.rb +6 -4
- data/spec/models/mongoid/user.rb +2 -0
- data/spec/models/newspaper.rb +2 -0
- data/spec/models/page.rb +2 -0
- data/spec/models/person.rb +2 -0
- data/spec/models/pet.rb +2 -0
- data/spec/models/post.rb +17 -0
- data/spec/models/relationship.rb +2 -0
- data/spec/models/reply.rb +2 -0
- data/spec/models/student.rb +2 -0
- data/spec/models/submission.rb +2 -0
- data/spec/models/teacher.rb +2 -0
- data/spec/models/user.rb +2 -0
- data/spec/models/writer.rb +2 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/support/bullet_ext.rb +2 -0
- data/spec/support/mongo_seed.rb +32 -30
- data/spec/support/rack_double.rb +9 -16
- data/spec/support/sqlite_seed.rb +84 -75
- data/tasks/bullet_tasks.rake +2 -0
- data/test.sh +2 -0
- data/update.sh +1 -0
- metadata +17 -9
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Notification
|
|
3
5
|
class Base
|
|
@@ -11,11 +13,11 @@ module Bullet
|
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def title
|
|
14
|
-
raise NoMethodError
|
|
16
|
+
raise NoMethodError, 'no method title defined'
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def body
|
|
18
|
-
raise NoMethodError
|
|
20
|
+
raise NoMethodError, 'no method body defined'
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def call_stack_messages
|
|
@@ -23,7 +25,11 @@ module Bullet
|
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def whoami
|
|
26
|
-
@user ||= ENV['USER'].presence || (
|
|
28
|
+
@user ||= ENV['USER'].presence || (begin
|
|
29
|
+
`whoami`.chomp
|
|
30
|
+
rescue StandardError
|
|
31
|
+
''
|
|
32
|
+
end)
|
|
27
33
|
if @user.present?
|
|
28
34
|
"user: #{@user}"
|
|
29
35
|
else
|
|
@@ -36,11 +42,11 @@ module Bullet
|
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def notify_inline
|
|
39
|
-
|
|
45
|
+
notifier.inline_notify(notification_data)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
def notify_out_of_channel
|
|
43
|
-
|
|
49
|
+
notifier.out_of_channel_notify(notification_data)
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
def short_notice
|
|
@@ -49,10 +55,10 @@ module Bullet
|
|
|
49
55
|
|
|
50
56
|
def notification_data
|
|
51
57
|
{
|
|
52
|
-
:
|
|
53
|
-
:
|
|
54
|
-
:
|
|
55
|
-
:
|
|
58
|
+
user: whoami,
|
|
59
|
+
url: url,
|
|
60
|
+
title: title,
|
|
61
|
+
body: body_with_caller
|
|
56
62
|
}
|
|
57
63
|
end
|
|
58
64
|
|
|
@@ -66,13 +72,13 @@ module Bullet
|
|
|
66
72
|
|
|
67
73
|
protected
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
def klazz_associations_str
|
|
76
|
+
" #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
|
|
77
|
+
end
|
|
72
78
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
def associations_str
|
|
80
|
+
":includes => #{@associations.map { |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
|
|
81
|
+
end
|
|
76
82
|
end
|
|
77
83
|
end
|
|
78
84
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Notification
|
|
3
5
|
class NPlusOneQuery < Base
|
|
@@ -17,15 +19,15 @@ module Bullet
|
|
|
17
19
|
|
|
18
20
|
def notification_data
|
|
19
21
|
super.merge(
|
|
20
|
-
:
|
|
22
|
+
backtrace: []
|
|
21
23
|
)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
protected
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
def call_stack_messages
|
|
29
|
+
(['Call stack'] + @callers).join("\n ")
|
|
30
|
+
end
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Notification
|
|
3
5
|
class UnusedEagerLoading < Base
|
|
@@ -17,15 +19,15 @@ module Bullet
|
|
|
17
19
|
|
|
18
20
|
def notification_data
|
|
19
21
|
super.merge(
|
|
20
|
-
|
|
22
|
+
backtrace: []
|
|
21
23
|
)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
protected
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
def call_stack_messages
|
|
29
|
+
(['Call stack'] + @callers).join("\n ")
|
|
30
|
+
end
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
end
|
data/lib/bullet/notification.rb
CHANGED
data/lib/bullet/rack.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
class Rack
|
|
3
5
|
include Dependency
|
|
@@ -8,17 +10,23 @@ module Bullet
|
|
|
8
10
|
|
|
9
11
|
def call(env)
|
|
10
12
|
return @app.call(env) unless Bullet.enable?
|
|
13
|
+
|
|
11
14
|
Bullet.start_request
|
|
12
15
|
status, headers, response = @app.call(env)
|
|
13
16
|
|
|
14
17
|
response_body = nil
|
|
15
18
|
if Bullet.notification?
|
|
16
|
-
if !file?(headers) && !sse?(headers) && !empty?(response) &&
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
|
20
|
+
if html_request?(headers, response)
|
|
21
|
+
response_body = response_body(response)
|
|
22
|
+
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
|
23
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
24
|
+
response_body = append_to_html_body(response_body, xhr_script)
|
|
25
|
+
headers['Content-Length'] = response_body.bytesize.to_s
|
|
26
|
+
else
|
|
27
|
+
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
|
28
|
+
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
|
29
|
+
end
|
|
22
30
|
end
|
|
23
31
|
Bullet.perform_out_of_channel_notifications(env)
|
|
24
32
|
end
|
|
@@ -29,29 +37,32 @@ module Bullet
|
|
|
29
37
|
|
|
30
38
|
# fix issue if response's body is a Proc
|
|
31
39
|
def empty?(response)
|
|
32
|
-
# response may be ["Not Found"], ["Move Permanently"], etc
|
|
33
|
-
if
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
!response_body(response).respond_to?(:empty?) ||
|
|
37
|
-
response_body(response).empty?
|
|
38
|
-
else
|
|
39
|
-
body = response_body(response)
|
|
40
|
-
body.nil? || body.empty?
|
|
41
|
-
end
|
|
40
|
+
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
|
41
|
+
# those should not happen if the status is 200
|
|
42
|
+
body = response_body(response)
|
|
43
|
+
body.nil? || body.empty?
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def append_to_html_body(response_body, content)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
body = response_body.dup
|
|
48
|
+
if body.include?('</body>')
|
|
49
|
+
position = body.rindex('</body>')
|
|
50
|
+
body.insert(position, content)
|
|
48
51
|
else
|
|
49
|
-
|
|
52
|
+
body << content
|
|
50
53
|
end
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def footer_note
|
|
54
|
-
"<div #{footer_div_attributes}>" +
|
|
57
|
+
"<div #{footer_div_attributes}>" + footer_header + '<br>' + Bullet.footer_info.uniq.join('<br>') + '</div>'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_header(headers, header_name, header_array)
|
|
61
|
+
# Many proxy applications such as Nginx and AWS ELB limit
|
|
62
|
+
# the size a header to 8KB, so truncate the list of reports to
|
|
63
|
+
# be under that limit
|
|
64
|
+
header_array.pop while header_array.to_json.length > 8 * 1024
|
|
65
|
+
headers[header_name] = header_array.to_json
|
|
55
66
|
end
|
|
56
67
|
|
|
57
68
|
def file?(headers)
|
|
@@ -63,7 +74,7 @@ module Bullet
|
|
|
63
74
|
end
|
|
64
75
|
|
|
65
76
|
def html_request?(headers, response)
|
|
66
|
-
headers['Content-Type']
|
|
77
|
+
headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
|
|
67
78
|
end
|
|
68
79
|
|
|
69
80
|
def response_body(response)
|
|
@@ -77,17 +88,27 @@ module Bullet
|
|
|
77
88
|
private
|
|
78
89
|
|
|
79
90
|
def footer_div_attributes
|
|
80
|
-
|
|
81
|
-
data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
EOF
|
|
91
|
+
<<~EOF
|
|
92
|
+
id="bullet-footer" data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
|
|
93
|
+
-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
|
|
94
|
+
-moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
|
|
95
|
+
padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
|
|
96
|
+
color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
|
|
97
|
+
EOF
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def footer_header
|
|
101
|
+
cancel_button = "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>×</span>"
|
|
102
|
+
if Bullet.console_enabled?
|
|
103
|
+
"<span>See 'Uniform Notifier' in JS Console for Stacktrace</span>#{cancel_button}"
|
|
104
|
+
else
|
|
105
|
+
cancel_button
|
|
106
|
+
end
|
|
87
107
|
end
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
# Make footer work for XHR requests by appending data to the footer
|
|
110
|
+
def xhr_script
|
|
111
|
+
"<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
|
|
91
112
|
end
|
|
92
113
|
end
|
|
93
114
|
end
|
data/lib/bullet/registry/base.rb
CHANGED
data/lib/bullet/registry.rb
CHANGED
|
@@ -1,35 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module StackTraceFilter
|
|
3
|
-
VENDOR_PATH = '/vendor'
|
|
5
|
+
VENDOR_PATH = '/vendor'
|
|
4
6
|
|
|
5
7
|
def caller_in_project
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
caller_path = location
|
|
10
|
-
caller_path.include?(app_root) && !caller_path.include?(vendor_root) ||
|
|
11
|
-
Bullet.stacktrace_includes.any?
|
|
12
|
-
case include_pattern
|
|
13
|
-
when String
|
|
14
|
-
caller_path.include?(include_pattern)
|
|
15
|
-
when Regexp
|
|
16
|
-
caller_path =~ include_pattern
|
|
17
|
-
end
|
|
18
|
-
end
|
|
8
|
+
vendor_root = Bullet.app_root + VENDOR_PATH
|
|
9
|
+
bundler_path = Bundler.bundle_path.to_s
|
|
10
|
+
select_caller_locations do |location|
|
|
11
|
+
caller_path = location_as_path(location)
|
|
12
|
+
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
|
|
13
|
+
Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
|
|
19
14
|
end
|
|
20
15
|
end
|
|
21
16
|
|
|
22
17
|
def excluded_stacktrace_path?
|
|
23
18
|
Bullet.stacktrace_excludes.any? do |exclude_pattern|
|
|
24
|
-
caller_in_project.any?
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def pattern_matches?(location, pattern)
|
|
26
|
+
path = location_as_path(location)
|
|
27
|
+
case pattern
|
|
28
|
+
when Array
|
|
29
|
+
pattern_path = pattern.first
|
|
30
|
+
filter = pattern.last
|
|
31
|
+
return false unless pattern_matches?(location, pattern_path)
|
|
32
|
+
|
|
33
|
+
case filter
|
|
34
|
+
when Range
|
|
35
|
+
filter.include?(location.lineno)
|
|
36
|
+
when Integer
|
|
37
|
+
filter == location.lineno
|
|
38
|
+
when String
|
|
39
|
+
filter == location.base_label
|
|
31
40
|
end
|
|
41
|
+
when String
|
|
42
|
+
path.include?(pattern)
|
|
43
|
+
when Regexp
|
|
44
|
+
path =~ pattern
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def location_as_path(location)
|
|
49
|
+
ruby_19? ? location : location.absolute_path.to_s
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def select_caller_locations
|
|
53
|
+
if ruby_19?
|
|
54
|
+
caller.select do |caller_path|
|
|
55
|
+
yield caller_path
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
caller_locations.select do |location|
|
|
59
|
+
yield location
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ruby_19?
|
|
65
|
+
if @ruby_19.nil?
|
|
66
|
+
@ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
|
32
67
|
end
|
|
68
|
+
@ruby_19
|
|
33
69
|
end
|
|
34
70
|
end
|
|
35
71
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'active_support/core_ext/module/delegation'
|
|
2
4
|
require 'set'
|
|
3
5
|
require 'uniform_notifier'
|
|
@@ -17,8 +19,8 @@ module Bullet
|
|
|
17
19
|
autoload :Registry, 'bullet/registry'
|
|
18
20
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
|
19
21
|
|
|
20
|
-
BULLET_DEBUG = 'BULLET_DEBUG'
|
|
21
|
-
TRUE = 'true'
|
|
22
|
+
BULLET_DEBUG = 'BULLET_DEBUG'
|
|
23
|
+
TRUE = 'true'
|
|
22
24
|
|
|
23
25
|
if defined? Rails::Railtie
|
|
24
26
|
class BulletRailtie < Rails::Railtie
|
|
@@ -29,21 +31,21 @@ module Bullet
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
class << self
|
|
32
|
-
attr_writer :
|
|
33
|
-
attr_reader :
|
|
34
|
+
attr_writer :n_plus_one_query_enable, :unused_eager_loading_enable, :counter_cache_enable, :stacktrace_includes, :stacktrace_excludes
|
|
35
|
+
attr_reader :whitelist
|
|
34
36
|
attr_accessor :add_footer, :orm_pathches_applied
|
|
35
37
|
|
|
36
38
|
available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.map { |notifier| "#{notifier}=" }
|
|
37
|
-
available_notifiers << { :
|
|
38
|
-
delegate
|
|
39
|
+
available_notifiers << { to: UniformNotifier }
|
|
40
|
+
delegate(*available_notifiers)
|
|
39
41
|
|
|
40
42
|
def raise=(should_raise)
|
|
41
43
|
UniformNotifier.raise = (should_raise ? Notification::UnoptimizedQueryError : false)
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
DETECTORS = [Bullet::Detector::NPlusOneQuery,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
Bullet::Detector::UnusedEagerLoading,
|
|
48
|
+
Bullet::Detector::CounterCache].freeze
|
|
47
49
|
|
|
48
50
|
def enable=(enable)
|
|
49
51
|
@enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
|
|
@@ -61,16 +63,20 @@ module Bullet
|
|
|
61
63
|
!!@enable
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
def app_root
|
|
67
|
+
(defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
64
70
|
def n_plus_one_query_enable?
|
|
65
|
-
|
|
71
|
+
enable? && !!@n_plus_one_query_enable
|
|
66
72
|
end
|
|
67
73
|
|
|
68
74
|
def unused_eager_loading_enable?
|
|
69
|
-
|
|
75
|
+
enable? && !!@unused_eager_loading_enable
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
def counter_cache_enable?
|
|
73
|
-
|
|
79
|
+
enable? && !!@counter_cache_enable
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
def stacktrace_includes
|
|
@@ -91,7 +97,7 @@ module Bullet
|
|
|
91
97
|
reset_whitelist
|
|
92
98
|
@whitelist[options[:type]][options[:class_name]] ||= []
|
|
93
99
|
@whitelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
|
|
94
|
-
@whitelist[options[:type]].delete_if { |
|
|
100
|
+
@whitelist[options[:type]].delete_if { |_key, val| val.empty? }
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
def get_whitelist_associations(type, class_name)
|
|
@@ -99,7 +105,7 @@ module Bullet
|
|
|
99
105
|
end
|
|
100
106
|
|
|
101
107
|
def reset_whitelist
|
|
102
|
-
@whitelist ||= { :
|
|
108
|
+
@whitelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
|
|
103
109
|
end
|
|
104
110
|
|
|
105
111
|
def clear_whitelist
|
|
@@ -109,9 +115,8 @@ module Bullet
|
|
|
109
115
|
def bullet_logger=(active)
|
|
110
116
|
if active
|
|
111
117
|
require 'fileutils'
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
bullet_log_file = File.open("#{root_path}/log/bullet.log", 'a+')
|
|
118
|
+
FileUtils.mkdir_p(app_root + '/log')
|
|
119
|
+
bullet_log_file = File.open("#{app_root}/log/bullet.log", 'a+')
|
|
115
120
|
bullet_log_file.sync = true
|
|
116
121
|
UniformNotifier.customized_logger = bullet_log_file
|
|
117
122
|
end
|
|
@@ -161,6 +166,7 @@ module Bullet
|
|
|
161
166
|
|
|
162
167
|
def notification?
|
|
163
168
|
return unless start?
|
|
169
|
+
|
|
164
170
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
165
171
|
notification_collector.notifications_present?
|
|
166
172
|
end
|
|
@@ -189,12 +195,19 @@ module Bullet
|
|
|
189
195
|
info
|
|
190
196
|
end
|
|
191
197
|
|
|
198
|
+
def text_notifications
|
|
199
|
+
info = []
|
|
200
|
+
notification_collector.collection.each do |notification|
|
|
201
|
+
info << notification.notification_data.values.compact.join("\n")
|
|
202
|
+
end
|
|
203
|
+
info
|
|
204
|
+
end
|
|
205
|
+
|
|
192
206
|
def warnings
|
|
193
|
-
notification_collector.collection.
|
|
207
|
+
notification_collector.collection.each_with_object({}) do |notification, warnings|
|
|
194
208
|
warning_type = notification.class.to_s.split(':').last.tableize
|
|
195
209
|
warnings[warning_type] ||= []
|
|
196
210
|
warnings[warning_type] << notification
|
|
197
|
-
warnings
|
|
198
211
|
end
|
|
199
212
|
end
|
|
200
213
|
|
|
@@ -217,25 +230,29 @@ module Bullet
|
|
|
217
230
|
return_value
|
|
218
231
|
end
|
|
219
232
|
|
|
233
|
+
def console_enabled?
|
|
234
|
+
UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
|
|
235
|
+
end
|
|
236
|
+
|
|
220
237
|
private
|
|
221
238
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
end
|
|
239
|
+
def for_each_active_notifier_with_notification
|
|
240
|
+
UniformNotifier.active_notifiers.each do |notifier|
|
|
241
|
+
notification_collector.collection.each do |notification|
|
|
242
|
+
notification.notifier = notifier
|
|
243
|
+
yield notification
|
|
228
244
|
end
|
|
229
245
|
end
|
|
246
|
+
end
|
|
230
247
|
|
|
231
|
-
|
|
232
|
-
|
|
248
|
+
def build_request_uri(env)
|
|
249
|
+
return "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']}" if env['REQUEST_URI']
|
|
233
250
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
end
|
|
251
|
+
if env['QUERY_STRING'].present?
|
|
252
|
+
"#{env['REQUEST_METHOD']} #{env['PATH_INFO']}?#{env['QUERY_STRING']}"
|
|
253
|
+
else
|
|
254
|
+
"#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
|
|
239
255
|
end
|
|
256
|
+
end
|
|
240
257
|
end
|
|
241
258
|
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Generators
|
|
3
5
|
class InstallGenerator < ::Rails::Generators::Base
|
|
4
|
-
desc
|
|
5
|
-
Description:
|
|
6
|
-
|
|
6
|
+
desc <<~DESC
|
|
7
|
+
Description:
|
|
8
|
+
Enable bullet in development/test for your application.
|
|
7
9
|
DESC
|
|
8
10
|
|
|
9
11
|
def enable_in_development
|
data/perf/benchmark.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH << 'lib'
|
|
2
4
|
require 'benchmark'
|
|
3
5
|
require 'rails'
|
|
4
6
|
require 'active_record'
|
|
@@ -27,13 +29,13 @@ class User < ActiveRecord::Base
|
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
# create database bullet_benchmark;
|
|
30
|
-
ActiveRecord::Base.establish_connection(:
|
|
32
|
+
ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'bullet_benchmark', server: '/tmp/mysql.socket', username: 'root')
|
|
31
33
|
|
|
32
34
|
ActiveRecord::Base.connection.tables.each do |table|
|
|
33
35
|
ActiveRecord::Base.connection.drop_table(table)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
|
-
ActiveRecord::Schema.define(:
|
|
38
|
+
ActiveRecord::Schema.define(version: 1) do
|
|
37
39
|
create_table :posts do |t|
|
|
38
40
|
t.column :title, :string
|
|
39
41
|
t.column :body, :string
|
|
@@ -56,21 +58,21 @@ posts_size = 1000
|
|
|
56
58
|
comments_size = 10_000
|
|
57
59
|
users = []
|
|
58
60
|
users_size.times do |i|
|
|
59
|
-
users << User.new(:
|
|
61
|
+
users << User.new(name: "user#{i}")
|
|
60
62
|
end
|
|
61
63
|
User.import users
|
|
62
64
|
users = User.all
|
|
63
65
|
|
|
64
66
|
posts = []
|
|
65
67
|
posts_size.times do |i|
|
|
66
|
-
posts << Post.new(:
|
|
68
|
+
posts << Post.new(title: "Title #{i}", body: "Body #{i}", user: users[i % 100])
|
|
67
69
|
end
|
|
68
70
|
Post.import posts
|
|
69
71
|
posts = Post.all
|
|
70
72
|
|
|
71
73
|
comments = []
|
|
72
74
|
comments_size.times do |i|
|
|
73
|
-
comments << Comment.new(:
|
|
75
|
+
comments << Comment.new(body: "Comment #{i}", post: posts[i % 1000], user: users[i % 100])
|
|
74
76
|
end
|
|
75
77
|
Comment.import comments
|
|
76
78
|
|
|
@@ -82,7 +84,7 @@ Benchmark.bm(70) do |bm|
|
|
|
82
84
|
bm.report("Querying & Iterating #{posts_size} Posts with #{comments_size} Comments and #{users_size} Users") do
|
|
83
85
|
10.times do
|
|
84
86
|
Bullet.start_request
|
|
85
|
-
Post.select('SQL_NO_CACHE *').includes(:user, :
|
|
87
|
+
Post.select('SQL_NO_CACHE *').includes(:user, comments: :user).each do |p|
|
|
86
88
|
p.title
|
|
87
89
|
p.user.name
|
|
88
90
|
p.comments.each do |c|
|
data/rails/init.rb
CHANGED