bullet 5.7.5 → 6.1.4
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 +22 -1
- data/CHANGELOG.md +49 -12
- 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/Gemfile.rails-6.1 +15 -0
- data/README.md +38 -13
- data/Rakefile +1 -1
- data/bullet.gemspec +8 -3
- data/lib/bullet.rb +50 -22
- data/lib/bullet/active_job.rb +13 -0
- data/lib/bullet/active_record4.rb +12 -35
- data/lib/bullet/active_record41.rb +10 -30
- data/lib/bullet/active_record42.rb +12 -27
- data/lib/bullet/active_record5.rb +197 -177
- data/lib/bullet/active_record52.rb +191 -166
- data/lib/bullet/active_record60.rb +278 -0
- data/lib/bullet/active_record61.rb +278 -0
- data/lib/bullet/bullet_xhr.js +63 -0
- data/lib/bullet/dependency.rb +54 -34
- data/lib/bullet/detector/association.rb +26 -20
- data/lib/bullet/detector/base.rb +1 -2
- data/lib/bullet/detector/counter_cache.rb +14 -9
- data/lib/bullet/detector/n_plus_one_query.rb +27 -17
- data/lib/bullet/detector/unused_eager_loading.rb +7 -3
- data/lib/bullet/ext/object.rb +5 -3
- data/lib/bullet/ext/string.rb +1 -1
- data/lib/bullet/mongoid4x.rb +4 -7
- data/lib/bullet/mongoid5x.rb +4 -7
- data/lib/bullet/mongoid6x.rb +8 -11
- data/lib/bullet/mongoid7x.rb +57 -0
- data/lib/bullet/notification/base.rb +15 -19
- data/lib/bullet/notification/n_plus_one_query.rb +2 -4
- data/lib/bullet/notification/unused_eager_loading.rb +2 -4
- data/lib/bullet/rack.rb +54 -28
- data/lib/bullet/stack_trace_filter.rb +39 -30
- data/lib/bullet/version.rb +1 -1
- data/lib/generators/bullet/install_generator.rb +26 -26
- data/perf/benchmark.rb +8 -14
- data/spec/bullet/detector/counter_cache_spec.rb +6 -6
- data/spec/bullet/detector/n_plus_one_query_spec.rb +30 -3
- data/spec/bullet/detector/unused_eager_loading_spec.rb +19 -6
- data/spec/bullet/ext/object_spec.rb +9 -4
- data/spec/bullet/notification/base_spec.rb +1 -3
- data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
- data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
- data/spec/bullet/rack_spec.rb +140 -5
- data/spec/bullet/registry/association_spec.rb +2 -2
- data/spec/bullet/registry/base_spec.rb +1 -1
- data/spec/bullet_spec.rb +10 -29
- data/spec/integration/active_record/association_spec.rb +122 -118
- data/spec/integration/counter_cache_spec.rb +11 -31
- data/spec/integration/mongoid/association_spec.rb +18 -32
- data/spec/models/attachment.rb +5 -0
- data/spec/models/client.rb +2 -0
- data/spec/models/firm.rb +1 -0
- data/spec/models/folder.rb +1 -2
- data/spec/models/group.rb +3 -0
- data/spec/models/page.rb +1 -2
- data/spec/models/post.rb +15 -0
- data/spec/models/submission.rb +1 -0
- data/spec/models/user.rb +1 -0
- data/spec/models/writer.rb +1 -2
- data/spec/spec_helper.rb +6 -10
- data/spec/support/bullet_ext.rb +8 -9
- data/spec/support/mongo_seed.rb +2 -16
- data/spec/support/sqlite_seed.rb +17 -2
- data/test.sh +2 -0
- data/update.sh +1 -0
- metadata +24 -11
@@ -8,7 +8,8 @@ module Bullet
|
|
8
8
|
|
9
9
|
def initialize(base_class, association_or_associations, path = nil)
|
10
10
|
@base_class = base_class
|
11
|
-
@associations =
|
11
|
+
@associations =
|
12
|
+
association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]
|
12
13
|
@path = path
|
13
14
|
end
|
14
15
|
|
@@ -25,16 +26,16 @@ module Bullet
|
|
25
26
|
end
|
26
27
|
|
27
28
|
def whoami
|
28
|
-
@user ||=
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
@user ||=
|
30
|
+
ENV['USER'].presence ||
|
31
|
+
(
|
32
|
+
begin
|
33
|
+
`whoami`.chomp
|
34
|
+
rescue StandardError
|
35
|
+
''
|
36
|
+
end
|
37
|
+
)
|
38
|
+
@user.present? ? "user: #{@user}" : ''
|
38
39
|
end
|
39
40
|
|
40
41
|
def body_with_caller
|
@@ -54,12 +55,7 @@ module Bullet
|
|
54
55
|
end
|
55
56
|
|
56
57
|
def notification_data
|
57
|
-
{
|
58
|
-
user: whoami,
|
59
|
-
url: url,
|
60
|
-
title: title,
|
61
|
-
body: body_with_caller
|
62
|
-
}
|
58
|
+
{ user: whoami, url: url, title: title, body: body_with_caller }
|
63
59
|
end
|
64
60
|
|
65
61
|
def eql?(other)
|
@@ -73,11 +69,11 @@ module Bullet
|
|
73
69
|
protected
|
74
70
|
|
75
71
|
def klazz_associations_str
|
76
|
-
" #{@base_class} => [#{@associations.map(&:inspect).join(', '
|
72
|
+
" #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
|
77
73
|
end
|
78
74
|
|
79
75
|
def associations_str
|
80
|
-
"
|
76
|
+
".includes(#{@associations.map { |a| a.to_s.to_sym }.inspect})"
|
81
77
|
end
|
82
78
|
end
|
83
79
|
end
|
@@ -10,7 +10,7 @@ module Bullet
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def body
|
13
|
-
"#{klazz_associations_str}\n Add to your
|
13
|
+
"#{klazz_associations_str}\n Add to your query: #{associations_str}"
|
14
14
|
end
|
15
15
|
|
16
16
|
def title
|
@@ -18,9 +18,7 @@ module Bullet
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def notification_data
|
21
|
-
super.merge(
|
22
|
-
backtrace: []
|
23
|
-
)
|
21
|
+
super.merge(backtrace: [])
|
24
22
|
end
|
25
23
|
|
26
24
|
protected
|
@@ -10,7 +10,7 @@ module Bullet
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def body
|
13
|
-
"#{klazz_associations_str}\n Remove from your
|
13
|
+
"#{klazz_associations_str}\n Remove from your query: #{associations_str}"
|
14
14
|
end
|
15
15
|
|
16
16
|
def title
|
@@ -18,9 +18,7 @@ module Bullet
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def notification_data
|
21
|
-
super.merge(
|
22
|
-
backtrace: []
|
23
|
-
)
|
21
|
+
super.merge(backtrace: [])
|
24
22
|
end
|
25
23
|
|
26
24
|
protected
|
data/lib/bullet/rack.rb
CHANGED
@@ -10,17 +10,24 @@ module Bullet
|
|
10
10
|
|
11
11
|
def call(env)
|
12
12
|
return @app.call(env) unless Bullet.enable?
|
13
|
+
|
13
14
|
Bullet.start_request
|
14
15
|
status, headers, response = @app.call(env)
|
15
16
|
|
16
17
|
response_body = nil
|
18
|
+
|
17
19
|
if Bullet.notification?
|
18
|
-
if !file?(headers) && !sse?(headers) && !empty?(response) &&
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
21
|
+
if html_request?(headers, response)
|
22
|
+
response_body = response_body(response)
|
23
|
+
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
24
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
25
|
+
response_body = append_to_html_body(response_body, xhr_script) if Bullet.add_footer && !Bullet.skip_http_headers
|
26
|
+
headers['Content-Length'] = response_body.bytesize.to_s
|
27
|
+
elsif !Bullet.skip_http_headers
|
28
|
+
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
29
|
+
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
30
|
+
end
|
24
31
|
end
|
25
32
|
Bullet.perform_out_of_channel_notifications(env)
|
26
33
|
end
|
@@ -31,20 +38,15 @@ module Bullet
|
|
31
38
|
|
32
39
|
# fix issue if response's body is a Proc
|
33
40
|
def empty?(response)
|
34
|
-
# response may be ["Not Found"], ["Move Permanently"], etc
|
35
|
-
if
|
36
|
-
|
37
|
-
|
38
|
-
!response_body(response).respond_to?(:empty?) ||
|
39
|
-
response_body(response).empty?
|
40
|
-
else
|
41
|
-
body = response_body(response)
|
42
|
-
body.nil? || body.empty?
|
43
|
-
end
|
41
|
+
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
42
|
+
# those should not happen if the status is 200
|
43
|
+
body = response_body(response)
|
44
|
+
body.nil? || body.empty?
|
44
45
|
end
|
45
46
|
|
46
47
|
def append_to_html_body(response_body, content)
|
47
48
|
body = response_body.dup
|
49
|
+
content = content.html_safe if content.respond_to?(:html_safe)
|
48
50
|
if body.include?('</body>')
|
49
51
|
position = body.rindex('</body>')
|
50
52
|
body.insert(position, content)
|
@@ -54,7 +56,15 @@ module Bullet
|
|
54
56
|
end
|
55
57
|
|
56
58
|
def footer_note
|
57
|
-
"<
|
59
|
+
"<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
|
60
|
+
end
|
61
|
+
|
62
|
+
def set_header(headers, header_name, header_array)
|
63
|
+
# Many proxy applications such as Nginx and AWS ELB limit
|
64
|
+
# the size a header to 8KB, so truncate the list of reports to
|
65
|
+
# be under that limit
|
66
|
+
header_array.pop while header_array.to_json.length > 8 * 1024
|
67
|
+
headers[header_name] = header_array.to_json
|
58
68
|
end
|
59
69
|
|
60
70
|
def file?(headers)
|
@@ -66,7 +76,7 @@ module Bullet
|
|
66
76
|
end
|
67
77
|
|
68
78
|
def html_request?(headers, response)
|
69
|
-
headers['Content-Type']
|
79
|
+
headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
|
70
80
|
end
|
71
81
|
|
72
82
|
def response_body(response)
|
@@ -79,18 +89,34 @@ module Bullet
|
|
79
89
|
|
80
90
|
private
|
81
91
|
|
82
|
-
def
|
83
|
-
|
84
|
-
data-is-bullet-footer
|
85
|
-
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
92
|
+
def details_attributes
|
93
|
+
<<~EOF
|
94
|
+
id="bullet-footer" data-is-bullet-footer
|
95
|
+
style="cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;"
|
96
|
+
EOF
|
97
|
+
end
|
98
|
+
|
99
|
+
def summary_attributes
|
100
|
+
<<~EOF
|
101
|
+
style="font-weight: 600; padding: 2px 8px"
|
102
|
+
EOF
|
103
|
+
end
|
104
|
+
|
105
|
+
def footer_content_attributes
|
106
|
+
<<~EOF
|
107
|
+
style="padding: 8px; border-top: 1px solid #9b1c1c;"
|
108
|
+
EOF
|
109
|
+
end
|
110
|
+
|
111
|
+
def footer_console_message
|
112
|
+
if Bullet.console_enabled?
|
113
|
+
"<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
|
114
|
+
end
|
90
115
|
end
|
91
116
|
|
92
|
-
|
93
|
-
|
117
|
+
# Make footer work for XHR requests by appending data to the footer
|
118
|
+
def xhr_script
|
119
|
+
"<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
|
94
120
|
end
|
95
121
|
end
|
96
122
|
end
|
@@ -2,51 +2,60 @@
|
|
2
2
|
|
3
3
|
module Bullet
|
4
4
|
module StackTraceFilter
|
5
|
-
VENDOR_PATH = '/vendor'
|
5
|
+
VENDOR_PATH = '/vendor'
|
6
|
+
IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
6
7
|
|
7
8
|
def caller_in_project
|
8
|
-
|
9
|
-
vendor_root = app_root + VENDOR_PATH
|
9
|
+
vendor_root = Bullet.app_root + VENDOR_PATH
|
10
10
|
bundler_path = Bundler.bundle_path.to_s
|
11
|
-
select_caller_locations do |
|
12
|
-
caller_path
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
caller_path.include?(include_pattern)
|
17
|
-
when Regexp
|
18
|
-
caller_path =~ include_pattern
|
19
|
-
end
|
20
|
-
end
|
11
|
+
select_caller_locations do |location|
|
12
|
+
caller_path = location_as_path(location)
|
13
|
+
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
14
|
+
!caller_path.include?(bundler_path) ||
|
15
|
+
Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
|
21
16
|
end
|
22
17
|
end
|
23
18
|
|
24
19
|
def excluded_stacktrace_path?
|
25
20
|
Bullet.stacktrace_excludes.any? do |exclude_pattern|
|
26
|
-
caller_in_project.any?
|
27
|
-
caller_path = location.absolute_path.to_s
|
28
|
-
case exclude_pattern
|
29
|
-
when String
|
30
|
-
caller_path.include?(exclude_pattern)
|
31
|
-
when Regexp
|
32
|
-
caller_path =~ exclude_pattern
|
33
|
-
end
|
34
|
-
end
|
21
|
+
caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
|
35
22
|
end
|
36
23
|
end
|
37
24
|
|
38
25
|
private
|
39
26
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
27
|
+
def pattern_matches?(location, pattern)
|
28
|
+
path = location_as_path(location)
|
29
|
+
case pattern
|
30
|
+
when Array
|
31
|
+
pattern_path = pattern.first
|
32
|
+
filter = pattern.last
|
33
|
+
return false unless pattern_matches?(location, pattern_path)
|
34
|
+
|
35
|
+
case filter
|
36
|
+
when Range
|
37
|
+
filter.include?(location.lineno)
|
38
|
+
when Integer
|
39
|
+
filter == location.lineno
|
40
|
+
when String
|
41
|
+
filter == location.base_label
|
44
42
|
end
|
43
|
+
when String
|
44
|
+
path.include?(pattern)
|
45
|
+
when Regexp
|
46
|
+
path =~ pattern
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def location_as_path(location)
|
51
|
+
IS_RUBY_19 ? location : location.absolute_path.to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
def select_caller_locations
|
55
|
+
if IS_RUBY_19
|
56
|
+
caller.select { |caller_path| yield caller_path }
|
45
57
|
else
|
46
|
-
caller_locations.select
|
47
|
-
caller_path = location.absolute_path.to_s
|
48
|
-
yield caller_path
|
49
|
-
end
|
58
|
+
caller_locations.select { |location| yield location }
|
50
59
|
end
|
51
60
|
end
|
52
61
|
end
|
data/lib/bullet/version.rb
CHANGED
@@ -3,24 +3,24 @@
|
|
3
3
|
module Bullet
|
4
4
|
module Generators
|
5
5
|
class InstallGenerator < ::Rails::Generators::Base
|
6
|
-
desc
|
7
|
-
Description:
|
8
|
-
|
6
|
+
desc <<~DESC
|
7
|
+
Description:
|
8
|
+
Enable bullet in development/test for your application.
|
9
9
|
DESC
|
10
10
|
|
11
11
|
def enable_in_development
|
12
12
|
environment(nil, env: 'development') do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
<<~FILE
|
14
|
+
config.after_initialize do
|
15
|
+
Bullet.enable = true
|
16
|
+
Bullet.alert = true
|
17
|
+
Bullet.bullet_logger = true
|
18
|
+
Bullet.console = true
|
19
|
+
# Bullet.growl = true
|
20
|
+
Bullet.rails_logger = true
|
21
|
+
Bullet.add_footer = true
|
22
|
+
end
|
23
|
+
|
24
24
|
FILE
|
25
25
|
end
|
26
26
|
|
@@ -28,20 +28,20 @@ Description:
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def enable_in_test
|
31
|
-
|
32
|
-
environment(nil, env: 'test') do
|
33
|
-
<<-"FILE".strip
|
34
|
-
|
35
|
-
config.after_initialize do
|
36
|
-
Bullet.enable = true
|
37
|
-
Bullet.bullet_logger = true
|
38
|
-
Bullet.raise = true # raise an error if n+1 query occurs
|
39
|
-
end
|
40
|
-
FILE
|
41
|
-
end
|
31
|
+
return unless yes?('Would you like to enable bullet in test environment? (y/n)')
|
42
32
|
|
43
|
-
|
33
|
+
environment(nil, env: 'test') do
|
34
|
+
<<~FILE
|
35
|
+
config.after_initialize do
|
36
|
+
Bullet.enable = true
|
37
|
+
Bullet.bullet_logger = true
|
38
|
+
Bullet.raise = true # raise an error if n+1 query occurs
|
39
|
+
end
|
40
|
+
|
41
|
+
FILE
|
44
42
|
end
|
43
|
+
|
44
|
+
say 'Enabled bullet in config/environments/test.rb'
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
data/perf/benchmark.rb
CHANGED
@@ -29,11 +29,11 @@ class User < ActiveRecord::Base
|
|
29
29
|
end
|
30
30
|
|
31
31
|
# create database bullet_benchmark;
|
32
|
-
ActiveRecord::Base.establish_connection(
|
32
|
+
ActiveRecord::Base.establish_connection(
|
33
|
+
adapter: 'mysql2', database: 'bullet_benchmark', server: '/tmp/mysql.socket', username: 'root'
|
34
|
+
)
|
33
35
|
|
34
|
-
ActiveRecord::Base.connection.tables.each
|
35
|
-
ActiveRecord::Base.connection.drop_table(table)
|
36
|
-
end
|
36
|
+
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
|
37
37
|
|
38
38
|
ActiveRecord::Schema.define(version: 1) do
|
39
39
|
create_table :posts do |t|
|
@@ -54,26 +54,20 @@ ActiveRecord::Schema.define(version: 1) do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
users_size = 100
|
57
|
-
posts_size =
|
57
|
+
posts_size = 1_000
|
58
58
|
comments_size = 10_000
|
59
59
|
users = []
|
60
|
-
users_size.times
|
61
|
-
users << User.new(name: "user#{i}")
|
62
|
-
end
|
60
|
+
users_size.times { |i| users << User.new(name: "user#{i}") }
|
63
61
|
User.import users
|
64
62
|
users = User.all
|
65
63
|
|
66
64
|
posts = []
|
67
|
-
posts_size.times
|
68
|
-
posts << Post.new(title: "Title #{i}", body: "Body #{i}", user: users[i % 100])
|
69
|
-
end
|
65
|
+
posts_size.times { |i| posts << Post.new(title: "Title #{i}", body: "Body #{i}", user: users[i % 100]) }
|
70
66
|
Post.import posts
|
71
67
|
posts = Post.all
|
72
68
|
|
73
69
|
comments = []
|
74
|
-
comments_size.times
|
75
|
-
comments << Comment.new(body: "Comment #{i}", post: posts[i % 1000], user: users[i % 100])
|
76
|
-
end
|
70
|
+
comments_size.times { |i| comments << Comment.new(body: "Comment #{i}", post: posts[i % 1_000], user: users[i % 100]) }
|
77
71
|
Comment.import comments
|
78
72
|
|
79
73
|
puts 'Start benchmarking...'
|