bullet 6.1.0 → 7.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +82 -0
- data/CHANGELOG.md +66 -0
- data/Gemfile.rails-6.0 +1 -1
- data/Gemfile.rails-6.1 +15 -0
- data/Gemfile.rails-7.0 +10 -0
- data/MIT-LICENSE +1 -1
- data/README.md +41 -27
- data/lib/bullet/active_job.rb +5 -1
- data/lib/bullet/active_record41.rb +1 -0
- data/lib/bullet/active_record42.rb +1 -0
- data/lib/bullet/active_record5.rb +10 -8
- data/lib/bullet/active_record52.rb +32 -25
- data/lib/bullet/active_record60.rb +30 -23
- data/lib/bullet/active_record61.rb +274 -0
- data/lib/bullet/active_record70.rb +284 -0
- data/lib/bullet/bullet_xhr.js +18 -17
- data/lib/bullet/dependency.rb +16 -0
- data/lib/bullet/detector/association.rb +8 -0
- data/lib/bullet/detector/base.rb +2 -1
- data/lib/bullet/detector/counter_cache.rb +2 -2
- data/lib/bullet/detector/n_plus_one_query.rb +24 -13
- data/lib/bullet/detector/unused_eager_loading.rb +3 -3
- data/lib/bullet/mongoid4x.rb +1 -1
- data/lib/bullet/mongoid5x.rb +1 -1
- data/lib/bullet/mongoid6x.rb +1 -1
- data/lib/bullet/mongoid7x.rb +32 -17
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +64 -23
- data/lib/bullet/registry/call_stack.rb +12 -0
- data/lib/bullet/registry.rb +1 -0
- data/lib/bullet/stack_trace_filter.rb +15 -15
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +35 -29
- data/lib/generators/bullet/install_generator.rb +22 -25
- data/perf/benchmark.rb +4 -1
- data/spec/bullet/detector/counter_cache_spec.rb +1 -1
- data/spec/bullet/detector/n_plus_one_query_spec.rb +1 -33
- data/spec/bullet/detector/unused_eager_loading_spec.rb +15 -10
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/notification/base_spec.rb +4 -4
- data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
- data/spec/bullet/rack_spec.rb +152 -9
- data/spec/bullet/stack_trace_filter_spec.rb +26 -0
- data/spec/bullet_spec.rb +15 -15
- data/spec/integration/active_record/association_spec.rb +95 -12
- data/spec/integration/counter_cache_spec.rb +4 -4
- data/spec/integration/mongoid/association_spec.rb +4 -4
- data/spec/models/attachment.rb +5 -0
- data/spec/models/deal.rb +5 -0
- data/spec/models/folder.rb +2 -1
- data/spec/models/group.rb +2 -1
- data/spec/models/page.rb +2 -1
- data/spec/models/post.rb +2 -0
- data/spec/models/role.rb +7 -0
- data/spec/models/submission.rb +1 -0
- data/spec/models/user.rb +2 -0
- data/spec/models/writer.rb +2 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/support/mongo_seed.rb +1 -0
- data/spec/support/sqlite_seed.rb +38 -0
- data/test.sh +2 -0
- metadata +20 -7
- data/.travis.yml +0 -33
data/lib/bullet/detector/base.rb
CHANGED
@@ -20,7 +20,7 @@ module Bullet
|
|
20
20
|
return unless Bullet.start?
|
21
21
|
return unless Bullet.counter_cache_enable?
|
22
22
|
|
23
|
-
objects = Array(object_or_objects)
|
23
|
+
objects = Array.wrap(object_or_objects)
|
24
24
|
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
25
25
|
|
26
26
|
Bullet.debug(
|
@@ -54,7 +54,7 @@ module Bullet
|
|
54
54
|
private
|
55
55
|
|
56
56
|
def create_notification(klazz, associations)
|
57
|
-
notify_associations = Array(associations) - Bullet.
|
57
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
|
58
58
|
|
59
59
|
if notify_associations.present?
|
60
60
|
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
|
@@ -7,7 +7,7 @@ module Bullet
|
|
7
7
|
extend StackTraceFilter
|
8
8
|
|
9
9
|
class << self
|
10
|
-
# executed when object.
|
10
|
+
# executed when object.associations is called.
|
11
11
|
# first, it keeps this method call for object.association.
|
12
12
|
# then, it checks if this associations call is unpreload.
|
13
13
|
# if it is, keeps this unpreload associations and caller.
|
@@ -25,7 +25,7 @@ module Bullet
|
|
25
25
|
)
|
26
26
|
if !excluded_stacktrace_path? && conditions_met?(object, associations)
|
27
27
|
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
|
28
|
-
create_notification caller_in_project, object.class.to_s, associations
|
28
|
+
create_notification caller_in_project(object.bullet_key), object.class.to_s, associations
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -33,14 +33,26 @@ module Bullet
|
|
33
33
|
return unless Bullet.start?
|
34
34
|
return unless Bullet.n_plus_one_query_enable?
|
35
35
|
|
36
|
-
objects = Array(object_or_objects)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
36
|
+
objects = Array.wrap(object_or_objects)
|
37
|
+
class_names_match_regex = true
|
38
|
+
primary_key_values_are_empty = true
|
39
|
+
keys_joined = ""
|
40
|
+
objects.each do |obj|
|
41
|
+
unless obj.class.name =~ /^HABTM_/
|
42
|
+
class_names_match_regex = false
|
43
|
+
end
|
44
|
+
unless obj.bullet_primary_key_value.nil?
|
45
|
+
primary_key_values_are_empty = false
|
46
|
+
end
|
47
|
+
keys_joined += "#{(keys_joined.empty?? '' : ', ')}#{obj.bullet_key}"
|
48
|
+
end
|
49
|
+
unless class_names_match_regex || primary_key_values_are_empty
|
50
|
+
Bullet.debug(
|
51
|
+
'Detector::NPlusOneQuery#add_possible_objects',
|
52
|
+
"objects: #{keys_joined}"
|
53
|
+
)
|
54
|
+
objects.each { |object| possible_objects.add object.bullet_key }
|
55
|
+
end
|
44
56
|
end
|
45
57
|
|
46
58
|
def add_impossible_object(object)
|
@@ -84,8 +96,7 @@ module Bullet
|
|
84
96
|
# associations == v comparison order is important here because
|
85
97
|
# v variable might be a squeel node where :== method is redefined,
|
86
98
|
# so it does not compare values at all and return unexpected results
|
87
|
-
result =
|
88
|
-
v.is_a?(Hash) ? v.key?(associations) : associations == v
|
99
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
89
100
|
return true if result
|
90
101
|
end
|
91
102
|
|
@@ -95,7 +106,7 @@ module Bullet
|
|
95
106
|
private
|
96
107
|
|
97
108
|
def create_notification(callers, klazz, associations)
|
98
|
-
notify_associations = Array(associations) - Bullet.
|
109
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
99
110
|
|
100
111
|
if notify_associations.present?
|
101
112
|
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
@@ -10,7 +10,7 @@ module Bullet
|
|
10
10
|
# check if there are unused preload associations.
|
11
11
|
# get related_objects from eager_loadings associated with object and associations
|
12
12
|
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
13
|
-
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload
|
13
|
+
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload associations
|
14
14
|
def check_unused_preload_associations
|
15
15
|
return unless Bullet.start?
|
16
16
|
return unless Bullet.unused_eager_loading_enable?
|
@@ -20,7 +20,7 @@ module Bullet
|
|
20
20
|
next if object_association_diff.empty?
|
21
21
|
|
22
22
|
Bullet.debug('detect unused preload', "object: #{bullet_key}, associations: #{object_association_diff}")
|
23
|
-
create_notification(caller_in_project, bullet_key.bullet_class_name, object_association_diff)
|
23
|
+
create_notification(caller_in_project(bullet_key), bullet_key.bullet_class_name, object_association_diff)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -65,7 +65,7 @@ module Bullet
|
|
65
65
|
private
|
66
66
|
|
67
67
|
def create_notification(callers, klazz, associations)
|
68
|
-
notify_associations = Array(associations) - Bullet.
|
68
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
|
69
69
|
|
70
70
|
if notify_associations.present?
|
71
71
|
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
|
data/lib/bullet/mongoid4x.rb
CHANGED
data/lib/bullet/mongoid5x.rb
CHANGED
data/lib/bullet/mongoid6x.rb
CHANGED
data/lib/bullet/mongoid7x.rb
CHANGED
@@ -4,35 +4,50 @@ module Bullet
|
|
4
4
|
module Mongoid
|
5
5
|
def self.enable
|
6
6
|
require 'mongoid'
|
7
|
+
require 'rubygems'
|
7
8
|
::Mongoid::Contextual::Mongo.class_eval do
|
8
9
|
alias_method :origin_first, :first
|
9
10
|
alias_method :origin_last, :last
|
10
11
|
alias_method :origin_each, :each
|
11
12
|
alias_method :origin_eager_load, :eager_load
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
result = origin_last(opts)
|
21
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
22
|
-
result
|
14
|
+
%i[first last].each do |context|
|
15
|
+
default = Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('7.5') ? nil : {}
|
16
|
+
define_method(context) do |opts = default|
|
17
|
+
result = send(:"origin_#{context}", opts)
|
18
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
19
|
+
result
|
20
|
+
end
|
23
21
|
end
|
24
22
|
|
25
23
|
def each(&block)
|
26
24
|
return to_enum unless block_given?
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
first_document = nil
|
27
|
+
document_count = 0
|
28
|
+
|
29
|
+
origin_each do |document|
|
30
|
+
document_count += 1
|
31
|
+
|
32
|
+
if document_count == 1
|
33
|
+
first_document = document
|
34
|
+
elsif document_count == 2
|
35
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
|
36
|
+
yield(first_document)
|
37
|
+
first_document = nil
|
38
|
+
yield(document)
|
39
|
+
else
|
40
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
|
41
|
+
yield(document)
|
42
|
+
end
|
34
43
|
end
|
35
|
-
|
44
|
+
|
45
|
+
if document_count == 1
|
46
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
|
47
|
+
yield(first_document)
|
48
|
+
end
|
49
|
+
|
50
|
+
self
|
36
51
|
end
|
37
52
|
|
38
53
|
def eager_load(docs)
|
data/lib/bullet/notification.rb
CHANGED
data/lib/bullet/rack.rb
CHANGED
@@ -4,6 +4,8 @@ module Bullet
|
|
4
4
|
class Rack
|
5
5
|
include Dependency
|
6
6
|
|
7
|
+
NONCE_MATCHER = /script-src .*'nonce-(?<nonce>[A-Za-z0-9+\/]+={0,2})'/
|
8
|
+
|
7
9
|
def initialize(app)
|
8
10
|
@app = app
|
9
11
|
end
|
@@ -17,14 +19,20 @@ module Bullet
|
|
17
19
|
response_body = nil
|
18
20
|
|
19
21
|
if Bullet.notification?
|
20
|
-
if
|
22
|
+
if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
21
23
|
if html_request?(headers, response)
|
22
24
|
response_body = response_body(response)
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
|
26
|
+
with_security_policy_nonce(headers) do |nonce|
|
27
|
+
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
28
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
29
|
+
if Bullet.add_footer && !Bullet.skip_http_headers
|
30
|
+
response_body = append_to_html_body(response_body, xhr_script(nonce))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
26
34
|
headers['Content-Length'] = response_body.bytesize.to_s
|
27
|
-
|
35
|
+
elsif !Bullet.skip_http_headers
|
28
36
|
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
29
37
|
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
30
38
|
end
|
@@ -40,12 +48,14 @@ module Bullet
|
|
40
48
|
def empty?(response)
|
41
49
|
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
42
50
|
# those should not happen if the status is 200
|
51
|
+
return true if !response.respond_to?(:body) && !response.respond_to?(:first)
|
43
52
|
body = response_body(response)
|
44
53
|
body.nil? || body.empty?
|
45
54
|
end
|
46
55
|
|
47
56
|
def append_to_html_body(response_body, content)
|
48
57
|
body = response_body.dup
|
58
|
+
content = content.html_safe if content.respond_to?(:html_safe)
|
49
59
|
if body.include?('</body>')
|
50
60
|
position = body.rindex('</body>')
|
51
61
|
body.insert(position, content)
|
@@ -55,14 +65,14 @@ module Bullet
|
|
55
65
|
end
|
56
66
|
|
57
67
|
def footer_note
|
58
|
-
"<
|
68
|
+
"<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
|
59
69
|
end
|
60
70
|
|
61
71
|
def set_header(headers, header_name, header_array)
|
62
72
|
# Many proxy applications such as Nginx and AWS ELB limit
|
63
73
|
# the size a header to 8KB, so truncate the list of reports to
|
64
74
|
# be under that limit
|
65
|
-
header_array.pop while header_array.to_json.length > 8 *
|
75
|
+
header_array.pop while header_array.to_json.length > 8 * 1024
|
66
76
|
headers[header_name] = header_array.to_json
|
67
77
|
end
|
68
78
|
|
@@ -75,42 +85,73 @@ module Bullet
|
|
75
85
|
end
|
76
86
|
|
77
87
|
def html_request?(headers, response)
|
78
|
-
headers['Content-Type']&.include?('text/html')
|
88
|
+
headers['Content-Type']&.include?('text/html')
|
79
89
|
end
|
80
90
|
|
81
91
|
def response_body(response)
|
82
92
|
if response.respond_to?(:body)
|
83
93
|
Array === response.body ? response.body.first : response.body
|
84
|
-
|
94
|
+
elsif response.respond_to?(:first)
|
85
95
|
response.first
|
86
96
|
end
|
87
97
|
end
|
88
98
|
|
89
99
|
private
|
90
100
|
|
91
|
-
def
|
101
|
+
def details_attributes
|
102
|
+
<<~EOF
|
103
|
+
id="bullet-footer" data-is-bullet-footer
|
104
|
+
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;"
|
105
|
+
EOF
|
106
|
+
end
|
107
|
+
|
108
|
+
def summary_attributes
|
109
|
+
<<~EOF
|
110
|
+
style="font-weight: 600; padding: 2px 8px"
|
111
|
+
EOF
|
112
|
+
end
|
113
|
+
|
114
|
+
def footer_content_attributes
|
92
115
|
<<~EOF
|
93
|
-
|
94
|
-
-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
|
95
|
-
-moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
|
96
|
-
padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
|
97
|
-
color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
|
116
|
+
style="padding: 8px; border-top: 1px solid #9b1c1c;"
|
98
117
|
EOF
|
99
118
|
end
|
100
119
|
|
101
|
-
def
|
102
|
-
cancel_button =
|
103
|
-
"<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>×</span>"
|
120
|
+
def footer_console_message
|
104
121
|
if Bullet.console_enabled?
|
105
|
-
"<span>See 'Uniform Notifier' in JS Console for Stacktrace</span
|
106
|
-
else
|
107
|
-
cancel_button
|
122
|
+
"<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
|
108
123
|
end
|
109
124
|
end
|
110
125
|
|
111
126
|
# Make footer work for XHR requests by appending data to the footer
|
112
|
-
def xhr_script
|
113
|
-
|
127
|
+
def xhr_script(nonce = nil)
|
128
|
+
script = File.read("#{__dir__}/bullet_xhr.js")
|
129
|
+
|
130
|
+
if nonce
|
131
|
+
"<script type='text/javascript' nonce='#{nonce}'>#{script}</script>"
|
132
|
+
else
|
133
|
+
"<script type='text/javascript'>#{script}</script>"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def with_security_policy_nonce(headers)
|
138
|
+
matched = (headers['Content-Security-Policy'] || '').match(NONCE_MATCHER)
|
139
|
+
nonce = matched[:nonce] if matched
|
140
|
+
|
141
|
+
if nonce
|
142
|
+
console_enabled = UniformNotifier.console
|
143
|
+
alert_enabled = UniformNotifier.alert
|
144
|
+
|
145
|
+
UniformNotifier.console = { attributes: { nonce: nonce } } if console_enabled
|
146
|
+
UniformNotifier.alert = { attributes: { nonce: nonce } } if alert_enabled
|
147
|
+
|
148
|
+
yield nonce
|
149
|
+
|
150
|
+
UniformNotifier.console = console_enabled
|
151
|
+
UniformNotifier.alert = alert_enabled
|
152
|
+
else
|
153
|
+
yield
|
154
|
+
end
|
114
155
|
end
|
115
156
|
end
|
116
157
|
end
|
data/lib/bullet/registry.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "bundler"
|
2
3
|
|
3
4
|
module Bullet
|
4
5
|
module StackTraceFilter
|
5
6
|
VENDOR_PATH = '/vendor'
|
7
|
+
IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
6
8
|
|
7
|
-
|
9
|
+
# @param bullet_key[String] - use this to get stored call stack from call_stacks object.
|
10
|
+
def caller_in_project(bullet_key = nil)
|
8
11
|
vendor_root = Bullet.app_root + VENDOR_PATH
|
9
12
|
bundler_path = Bundler.bundle_path.to_s
|
10
|
-
select_caller_locations do |location|
|
13
|
+
select_caller_locations(bullet_key) do |location|
|
11
14
|
caller_path = location_as_path(location)
|
12
15
|
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
13
|
-
!caller_path.include?(bundler_path) ||
|
14
|
-
|
16
|
+
!caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
|
17
|
+
pattern_matches?(location, include_pattern)
|
18
|
+
}
|
15
19
|
end
|
16
20
|
end
|
17
21
|
|
@@ -47,20 +51,16 @@ module Bullet
|
|
47
51
|
end
|
48
52
|
|
49
53
|
def location_as_path(location)
|
50
|
-
|
51
|
-
end
|
54
|
+
return location if location.is_a?(String)
|
52
55
|
|
53
|
-
|
54
|
-
if ruby_19?
|
55
|
-
caller.select { |caller_path| yield caller_path }
|
56
|
-
else
|
57
|
-
caller_locations.select { |location| yield location }
|
58
|
-
end
|
56
|
+
IS_RUBY_19 ? location : location.absolute_path.to_s
|
59
57
|
end
|
60
58
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
59
|
+
def select_caller_locations(bullet_key = nil)
|
60
|
+
return caller.select { |caller_path| yield caller_path } if IS_RUBY_19
|
61
|
+
|
62
|
+
call_stack = bullet_key ? call_stacks[bullet_key] : caller_locations
|
63
|
+
call_stack.select { |location| yield location }
|
64
64
|
end
|
65
65
|
end
|
66
66
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
@@ -20,13 +20,14 @@ module Bullet
|
|
20
20
|
autoload :Registry, 'bullet/registry'
|
21
21
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
22
22
|
|
23
|
-
BULLET_DEBUG = 'BULLET_DEBUG'
|
24
|
-
TRUE = 'true'
|
25
|
-
|
26
23
|
if defined?(Rails::Railtie)
|
27
24
|
class BulletRailtie < Rails::Railtie
|
28
25
|
initializer 'bullet.configure_rails_initialization' do |app|
|
29
|
-
|
26
|
+
if defined?(ActionDispatch::ContentSecurityPolicy::Middleware) && Rails.application.config.content_security_policy
|
27
|
+
app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, Bullet::Rack
|
28
|
+
else
|
29
|
+
app.middleware.use Bullet::Rack
|
30
|
+
end
|
30
31
|
end
|
31
32
|
end
|
32
33
|
end
|
@@ -38,10 +39,11 @@ module Bullet
|
|
38
39
|
:stacktrace_includes,
|
39
40
|
:stacktrace_excludes,
|
40
41
|
:skip_html_injection
|
41
|
-
attr_reader :
|
42
|
-
attr_accessor :add_footer, :orm_patches_applied
|
42
|
+
attr_reader :safelist
|
43
|
+
attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
|
43
44
|
|
44
|
-
available_notifiers =
|
45
|
+
available_notifiers =
|
46
|
+
UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
|
45
47
|
available_notifiers_options = { to: UniformNotifier }
|
46
48
|
delegate(*available_notifiers, **available_notifiers_options)
|
47
49
|
|
@@ -59,7 +61,7 @@ module Bullet
|
|
59
61
|
@enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
|
60
62
|
|
61
63
|
if enable?
|
62
|
-
|
64
|
+
reset_safelist
|
63
65
|
unless orm_patches_applied
|
64
66
|
self.orm_patches_applied = true
|
65
67
|
Bullet::Mongoid.enable if mongoid?
|
@@ -72,8 +74,9 @@ module Bullet
|
|
72
74
|
!!@enable
|
73
75
|
end
|
74
76
|
|
77
|
+
# Rails.root might be nil if `railties` is a dependency on a project that does not use Rails
|
75
78
|
def app_root
|
76
|
-
(defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
|
79
|
+
@app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s
|
77
80
|
end
|
78
81
|
|
79
82
|
def n_plus_one_query_enable?
|
@@ -89,36 +92,36 @@ module Bullet
|
|
89
92
|
end
|
90
93
|
|
91
94
|
def stacktrace_includes
|
92
|
-
@stacktrace_includes
|
95
|
+
@stacktrace_includes ||= []
|
93
96
|
end
|
94
97
|
|
95
98
|
def stacktrace_excludes
|
96
|
-
@stacktrace_excludes
|
99
|
+
@stacktrace_excludes ||= []
|
97
100
|
end
|
98
101
|
|
99
|
-
def
|
100
|
-
|
101
|
-
@
|
102
|
-
@
|
102
|
+
def add_safelist(options)
|
103
|
+
reset_safelist
|
104
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
105
|
+
@safelist[options[:type]][options[:class_name]] << options[:association].to_sym
|
103
106
|
end
|
104
107
|
|
105
|
-
def
|
106
|
-
|
107
|
-
@
|
108
|
-
@
|
109
|
-
@
|
108
|
+
def delete_safelist(options)
|
109
|
+
reset_safelist
|
110
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
111
|
+
@safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
|
112
|
+
@safelist[options[:type]].delete_if { |_key, val| val.empty? }
|
110
113
|
end
|
111
114
|
|
112
|
-
def
|
113
|
-
Array(@
|
115
|
+
def get_safelist_associations(type, class_name)
|
116
|
+
Array.wrap(@safelist[type][class_name])
|
114
117
|
end
|
115
118
|
|
116
|
-
def
|
117
|
-
@
|
119
|
+
def reset_safelist
|
120
|
+
@safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
|
118
121
|
end
|
119
122
|
|
120
|
-
def
|
121
|
-
@
|
123
|
+
def clear_safelist
|
124
|
+
@safelist = nil
|
122
125
|
end
|
123
126
|
|
124
127
|
def bullet_logger=(active)
|
@@ -132,7 +135,7 @@ module Bullet
|
|
132
135
|
end
|
133
136
|
|
134
137
|
def debug(title, message)
|
135
|
-
puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] ==
|
138
|
+
puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
|
136
139
|
end
|
137
140
|
|
138
141
|
def start_request
|
@@ -145,6 +148,7 @@ module Bullet
|
|
145
148
|
Thread.current[:bullet_impossible_objects] = Bullet::Registry::Object.new
|
146
149
|
Thread.current[:bullet_inversed_objects] = Bullet::Registry::Base.new
|
147
150
|
Thread.current[:bullet_eager_loadings] = Bullet::Registry::Association.new
|
151
|
+
Thread.current[:bullet_call_stacks] = Bullet::Registry::CallStack.new
|
148
152
|
|
149
153
|
Thread.current[:bullet_counter_possible_objects] ||= Bullet::Registry::Object.new
|
150
154
|
Thread.current[:bullet_counter_impossible_objects] ||= Bullet::Registry::Object.new
|
@@ -240,8 +244,10 @@ module Bullet
|
|
240
244
|
UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
|
241
245
|
end
|
242
246
|
|
243
|
-
def
|
244
|
-
@skip_html_injection
|
247
|
+
def inject_into_page?
|
248
|
+
return false if defined?(@skip_html_injection) && @skip_html_injection
|
249
|
+
|
250
|
+
console_enabled? || add_footer
|
245
251
|
end
|
246
252
|
|
247
253
|
private
|