bullet 6.1.3 → 7.0.1
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 +4 -4
- data/.github/workflows/main.yml +82 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile.rails-7.0 +10 -0
- data/README.md +12 -10
- data/lib/bullet/active_record41.rb +1 -0
- data/lib/bullet/active_record42.rb +1 -0
- data/lib/bullet/active_record52.rb +11 -17
- data/lib/bullet/active_record60.rb +11 -17
- data/lib/bullet/active_record61.rb +11 -17
- data/lib/bullet/active_record70.rb +275 -0
- data/lib/bullet/bullet_xhr.js +1 -0
- data/lib/bullet/dependency.rb +10 -0
- data/lib/bullet/detector/base.rb +2 -1
- data/lib/bullet/detector/counter_cache.rb +1 -1
- data/lib/bullet/detector/n_plus_one_query.rb +3 -3
- data/lib/bullet/detector/unused_eager_loading.rb +1 -1
- data/lib/bullet/mongoid7x.rb +26 -9
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +6 -3
- data/lib/bullet/stack_trace_filter.rb +7 -8
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +23 -24
- data/perf/benchmark.rb +4 -1
- data/spec/bullet/detector/unused_eager_loading_spec.rb +6 -2
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/rack_spec.rb +90 -13
- data/spec/bullet_spec.rb +15 -15
- data/spec/integration/active_record/association_spec.rb +27 -8
- data/spec/integration/counter_cache_spec.rb +4 -4
- data/spec/integration/mongoid/association_spec.rb +1 -1
- 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/user.rb +1 -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 +30 -0
- data/test.sh +2 -0
- metadata +10 -4
- data/.travis.yml +0 -33
@@ -35,6 +35,7 @@ module Bullet
|
|
35
35
|
|
36
36
|
objects = Array(object_or_objects)
|
37
37
|
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
38
|
+
return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
|
38
39
|
|
39
40
|
Bullet.debug(
|
40
41
|
'Detector::NPlusOneQuery#add_possible_objects',
|
@@ -84,8 +85,7 @@ module Bullet
|
|
84
85
|
# associations == v comparison order is important here because
|
85
86
|
# v variable might be a squeel node where :== method is redefined,
|
86
87
|
# so it does not compare values at all and return unexpected results
|
87
|
-
result =
|
88
|
-
v.is_a?(Hash) ? v.key?(associations) : associations == v
|
88
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
89
89
|
return true if result
|
90
90
|
end
|
91
91
|
|
@@ -95,7 +95,7 @@ module Bullet
|
|
95
95
|
private
|
96
96
|
|
97
97
|
def create_notification(callers, klazz, associations)
|
98
|
-
notify_associations = Array(associations) - Bullet.
|
98
|
+
notify_associations = Array(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
99
99
|
|
100
100
|
if notify_associations.present?
|
101
101
|
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
@@ -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(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/mongoid7x.rb
CHANGED
@@ -23,16 +23,33 @@ module Bullet
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
26
|
-
return to_enum unless
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
return to_enum unless block_given?
|
27
|
+
|
28
|
+
first_document = nil
|
29
|
+
document_count = 0
|
30
|
+
|
31
|
+
origin_each do |document|
|
32
|
+
document_count += 1
|
33
|
+
|
34
|
+
if document_count == 1
|
35
|
+
first_document = document
|
36
|
+
elsif document_count == 2
|
37
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
|
38
|
+
yield(first_document)
|
39
|
+
first_document = nil
|
40
|
+
yield(document)
|
41
|
+
else
|
42
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
|
43
|
+
yield(document)
|
44
|
+
end
|
34
45
|
end
|
35
|
-
|
46
|
+
|
47
|
+
if document_count == 1
|
48
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
|
49
|
+
yield(first_document)
|
50
|
+
end
|
51
|
+
|
52
|
+
self
|
36
53
|
end
|
37
54
|
|
38
55
|
def eager_load(docs)
|
data/lib/bullet/notification.rb
CHANGED
data/lib/bullet/rack.rb
CHANGED
@@ -22,9 +22,11 @@ module Bullet
|
|
22
22
|
response_body = response_body(response)
|
23
23
|
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
24
24
|
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
25
|
-
|
25
|
+
if Bullet.add_footer && !Bullet.skip_http_headers
|
26
|
+
response_body = append_to_html_body(response_body, xhr_script)
|
27
|
+
end
|
26
28
|
headers['Content-Length'] = response_body.bytesize.to_s
|
27
|
-
|
29
|
+
elsif !Bullet.skip_http_headers
|
28
30
|
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
29
31
|
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
30
32
|
end
|
@@ -40,6 +42,7 @@ module Bullet
|
|
40
42
|
def empty?(response)
|
41
43
|
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
42
44
|
# those should not happen if the status is 200
|
45
|
+
return true if !response.respond_to?(:body) && !response.respond_to?(:first)
|
43
46
|
body = response_body(response)
|
44
47
|
body.nil? || body.empty?
|
45
48
|
end
|
@@ -82,7 +85,7 @@ module Bullet
|
|
82
85
|
def response_body(response)
|
83
86
|
if response.respond_to?(:body)
|
84
87
|
Array === response.body ? response.body.first : response.body
|
85
|
-
|
88
|
+
elsif response.respond_to?(:first)
|
86
89
|
response.first
|
87
90
|
end
|
88
91
|
end
|
@@ -1,8 +1,10 @@
|
|
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
|
def caller_in_project
|
8
10
|
vendor_root = Bullet.app_root + VENDOR_PATH
|
@@ -10,8 +12,9 @@ module Bullet
|
|
10
12
|
select_caller_locations do |location|
|
11
13
|
caller_path = location_as_path(location)
|
12
14
|
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
13
|
-
!caller_path.include?(bundler_path) ||
|
14
|
-
|
15
|
+
!caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
|
16
|
+
pattern_matches?(location, include_pattern)
|
17
|
+
}
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -47,19 +50,15 @@ module Bullet
|
|
47
50
|
end
|
48
51
|
|
49
52
|
def location_as_path(location)
|
50
|
-
|
53
|
+
IS_RUBY_19 ? location : location.absolute_path.to_s
|
51
54
|
end
|
52
55
|
|
53
56
|
def select_caller_locations
|
54
|
-
if
|
57
|
+
if IS_RUBY_19
|
55
58
|
caller.select { |caller_path| yield caller_path }
|
56
59
|
else
|
57
60
|
caller_locations.select { |location| yield location }
|
58
61
|
end
|
59
62
|
end
|
60
|
-
|
61
|
-
def ruby_19?
|
62
|
-
@ruby_19 ||= Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
63
|
-
end
|
64
63
|
end
|
65
64
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
@@ -20,9 +20,6 @@ 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|
|
@@ -38,10 +35,11 @@ module Bullet
|
|
38
35
|
:stacktrace_includes,
|
39
36
|
:stacktrace_excludes,
|
40
37
|
:skip_html_injection
|
41
|
-
attr_reader :
|
42
|
-
attr_accessor :add_footer, :orm_patches_applied
|
38
|
+
attr_reader :safelist
|
39
|
+
attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
|
43
40
|
|
44
|
-
available_notifiers =
|
41
|
+
available_notifiers =
|
42
|
+
UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
|
45
43
|
available_notifiers_options = { to: UniformNotifier }
|
46
44
|
delegate(*available_notifiers, **available_notifiers_options)
|
47
45
|
|
@@ -59,7 +57,7 @@ module Bullet
|
|
59
57
|
@enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
|
60
58
|
|
61
59
|
if enable?
|
62
|
-
|
60
|
+
reset_safelist
|
63
61
|
unless orm_patches_applied
|
64
62
|
self.orm_patches_applied = true
|
65
63
|
Bullet::Mongoid.enable if mongoid?
|
@@ -72,8 +70,9 @@ module Bullet
|
|
72
70
|
!!@enable
|
73
71
|
end
|
74
72
|
|
73
|
+
# Rails.root might be nil if `railties` is a dependency on a project that does not use Rails
|
75
74
|
def app_root
|
76
|
-
(defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
|
75
|
+
@app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s
|
77
76
|
end
|
78
77
|
|
79
78
|
def n_plus_one_query_enable?
|
@@ -96,29 +95,29 @@ module Bullet
|
|
96
95
|
@stacktrace_excludes ||= []
|
97
96
|
end
|
98
97
|
|
99
|
-
def
|
100
|
-
|
101
|
-
@
|
102
|
-
@
|
98
|
+
def add_safelist(options)
|
99
|
+
reset_safelist
|
100
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
101
|
+
@safelist[options[:type]][options[:class_name]] << options[:association].to_sym
|
103
102
|
end
|
104
103
|
|
105
|
-
def
|
106
|
-
|
107
|
-
@
|
108
|
-
@
|
109
|
-
@
|
104
|
+
def delete_safelist(options)
|
105
|
+
reset_safelist
|
106
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
107
|
+
@safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
|
108
|
+
@safelist[options[:type]].delete_if { |_key, val| val.empty? }
|
110
109
|
end
|
111
110
|
|
112
|
-
def
|
113
|
-
Array(@
|
111
|
+
def get_safelist_associations(type, class_name)
|
112
|
+
Array(@safelist[type][class_name])
|
114
113
|
end
|
115
114
|
|
116
|
-
def
|
117
|
-
@
|
115
|
+
def reset_safelist
|
116
|
+
@safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
|
118
117
|
end
|
119
118
|
|
120
|
-
def
|
121
|
-
@
|
119
|
+
def clear_safelist
|
120
|
+
@safelist = nil
|
122
121
|
end
|
123
122
|
|
124
123
|
def bullet_logger=(active)
|
@@ -132,7 +131,7 @@ module Bullet
|
|
132
131
|
end
|
133
132
|
|
134
133
|
def debug(title, message)
|
135
|
-
puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] ==
|
134
|
+
puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
|
136
135
|
end
|
137
136
|
|
138
137
|
def start_request
|
data/perf/benchmark.rb
CHANGED
@@ -30,7 +30,10 @@ end
|
|
30
30
|
|
31
31
|
# create database bullet_benchmark;
|
32
32
|
ActiveRecord::Base.establish_connection(
|
33
|
-
adapter: 'mysql2',
|
33
|
+
adapter: 'mysql2',
|
34
|
+
database: 'bullet_benchmark',
|
35
|
+
server: '/tmp/mysql.socket',
|
36
|
+
username: 'root'
|
34
37
|
)
|
35
38
|
|
36
39
|
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
|
@@ -19,7 +19,9 @@ module Bullet
|
|
19
19
|
it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
|
20
20
|
UnusedEagerLoading.add_eager_loadings([@post], :association)
|
21
21
|
UnusedEagerLoading.add_call_object_associations(@post, :association)
|
22
|
-
expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
22
|
+
expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
23
|
+
[:association]
|
24
|
+
)
|
23
25
|
end
|
24
26
|
|
25
27
|
it 'should not get call associations if not exist in call_object_associations' do
|
@@ -30,7 +32,9 @@ module Bullet
|
|
30
32
|
|
31
33
|
context '.diff_object_associations' do
|
32
34
|
it 'should return associations not exist in call_association' do
|
33
|
-
expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
35
|
+
expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
36
|
+
[:association]
|
37
|
+
)
|
34
38
|
end
|
35
39
|
|
36
40
|
it 'should return empty if associations exist in call_association' do
|
@@ -10,7 +10,7 @@ describe Object do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
if mongoid?
|
13
|
-
it 'should return class with
|
13
|
+
it 'should return class with namespace and id composition' do
|
14
14
|
post = Mongoid::Post.first
|
15
15
|
expect(post.bullet_key).to eq("Mongoid::Post:#{post.id}")
|
16
16
|
end
|
data/spec/bullet/rack_spec.rb
CHANGED
@@ -54,6 +54,11 @@ module Bullet
|
|
54
54
|
response = double(body: '')
|
55
55
|
expect(middleware).to be_empty(response)
|
56
56
|
end
|
57
|
+
|
58
|
+
it 'should be true if no response body' do
|
59
|
+
response = double()
|
60
|
+
expect(middleware).to be_empty(response)
|
61
|
+
end
|
57
62
|
end
|
58
63
|
|
59
64
|
context '#call' do
|
@@ -90,7 +95,7 @@ module Bullet
|
|
90
95
|
before do
|
91
96
|
expect(Bullet).to receive(:notification?).and_return(true)
|
92
97
|
allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
93
|
-
allow(middleware).to receive(:xhr_script).and_return('')
|
98
|
+
allow(middleware).to receive(:xhr_script).and_return('<script></script>')
|
94
99
|
allow(middleware).to receive(:footer_note).and_return('footer')
|
95
100
|
expect(Bullet).to receive(:perform_out_of_channel_notifications)
|
96
101
|
end
|
@@ -99,21 +104,28 @@ module Bullet
|
|
99
104
|
expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
|
100
105
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
101
106
|
|
102
|
-
expect(headers['Content-Length']).to eq((
|
103
|
-
expect(response
|
104
|
-
expect(response.first).to include('<bullet></bullet><')
|
107
|
+
expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
|
108
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
|
105
109
|
end
|
106
110
|
|
107
111
|
it 'should change response body for html safe string if add_footer is true' do
|
108
112
|
expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
|
109
|
-
app.response =
|
110
|
-
|
111
|
-
|
113
|
+
app.response =
|
114
|
+
Support::ResponseDouble.new.tap do |response|
|
115
|
+
response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
|
116
|
+
end
|
112
117
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
113
118
|
|
114
|
-
expect(headers['Content-Length']).to eq((
|
115
|
-
expect(response
|
116
|
-
|
119
|
+
expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
|
120
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should add the footer-text header for non-html requests when add_footer is true' do
|
124
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
125
|
+
allow(Bullet).to receive(:footer_info).and_return(['footer text'])
|
126
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
127
|
+
_, headers, _response = middleware.call({})
|
128
|
+
expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
|
117
129
|
end
|
118
130
|
|
119
131
|
it 'should change response body if console_enabled is true' do
|
@@ -125,20 +137,71 @@ module Bullet
|
|
125
137
|
|
126
138
|
it 'should change response body for html safe string if console_enabled is true' do
|
127
139
|
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
128
|
-
app.response =
|
129
|
-
|
130
|
-
|
140
|
+
app.response =
|
141
|
+
Support::ResponseDouble.new.tap do |response|
|
142
|
+
response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
|
143
|
+
end
|
131
144
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
132
145
|
expect(headers['Content-Length']).to eq('56')
|
133
146
|
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
134
147
|
end
|
135
148
|
|
149
|
+
it 'should add headers for non-html requests when console_enabled is true' do
|
150
|
+
allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
|
151
|
+
allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
|
152
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
153
|
+
_, headers, _response = middleware.call({})
|
154
|
+
expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
|
155
|
+
end
|
156
|
+
|
136
157
|
it "shouldn't change response body unnecessarily" do
|
137
158
|
expected_response = Support::ResponseDouble.new 'Actual body'
|
138
159
|
app.response = expected_response
|
139
160
|
_, _, response = middleware.call({})
|
140
161
|
expect(response).to eq(expected_response)
|
141
162
|
end
|
163
|
+
|
164
|
+
it "shouldn't add headers unnecessarily" do
|
165
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
166
|
+
_, headers, _response = middleware.call({})
|
167
|
+
expect(headers).not_to include('X-bullet-footer-text')
|
168
|
+
expect(headers).not_to include('X-bullet-console-text')
|
169
|
+
end
|
170
|
+
|
171
|
+
context 'when skip_http_headers is enabled' do
|
172
|
+
before do
|
173
|
+
allow(Bullet).to receive(:skip_http_headers).and_return(true)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should include the footer but not the xhr script tag if add_footer is true' do
|
177
|
+
expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
178
|
+
_, headers, response = middleware.call({})
|
179
|
+
|
180
|
+
expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
|
181
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should not include the xhr script tag if console_enabled is true' do
|
185
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
186
|
+
_, headers, response = middleware.call({})
|
187
|
+
expect(headers['Content-Length']).to eq('56')
|
188
|
+
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should not add the footer-text header for non-html requests when add_footer is true' do
|
192
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
193
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
194
|
+
_, headers, _response = middleware.call({})
|
195
|
+
expect(headers).not_to include('X-bullet-footer-text')
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'should not add headers for non-html requests when console_enabled is true' do
|
199
|
+
allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
|
200
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
201
|
+
_, headers, _response = middleware.call({})
|
202
|
+
expect(headers).not_to include('X-bullet-console-text')
|
203
|
+
end
|
204
|
+
end
|
142
205
|
end
|
143
206
|
|
144
207
|
context 'when skip_html_injection is enabled' do
|
@@ -203,6 +266,20 @@ module Bullet
|
|
203
266
|
expect(middleware.response_body(response)).to eq body_string
|
204
267
|
end
|
205
268
|
end
|
269
|
+
|
270
|
+
begin
|
271
|
+
require 'rack/files'
|
272
|
+
|
273
|
+
context 'when `response` is a Rack::Files::Iterator' do
|
274
|
+
let(:response) { instance_double(::Rack::Files::Iterator) }
|
275
|
+
before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
|
276
|
+
|
277
|
+
it 'should return nil' do
|
278
|
+
expect(middleware.response_body(response)).to be_nil
|
279
|
+
end
|
280
|
+
end
|
281
|
+
rescue LoadError
|
282
|
+
end
|
206
283
|
end
|
207
284
|
end
|
208
285
|
end
|
data/spec/bullet_spec.rb
CHANGED
@@ -74,31 +74,31 @@ describe Bullet, focused: true do
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
describe '#
|
77
|
+
describe '#add_safelist' do
|
78
78
|
context "for 'special' class names" do
|
79
|
-
it 'is added to the
|
80
|
-
Bullet.
|
81
|
-
expect(Bullet.
|
79
|
+
it 'is added to the safelist successfully' do
|
80
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
81
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
82
82
|
end
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
|
-
describe '#
|
86
|
+
describe '#delete_safelist' do
|
87
87
|
context "for 'special' class names" do
|
88
|
-
it 'is deleted from the
|
89
|
-
Bullet.
|
90
|
-
Bullet.
|
91
|
-
expect(Bullet.
|
88
|
+
it 'is deleted from the safelist successfully' do
|
89
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
90
|
+
Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
91
|
+
expect(Bullet.safelist[:n_plus_one_query]).to eq({})
|
92
92
|
end
|
93
93
|
end
|
94
94
|
|
95
95
|
context 'when exists multiple definitions' do
|
96
|
-
it 'is deleted from the
|
97
|
-
Bullet.
|
98
|
-
Bullet.
|
99
|
-
Bullet.
|
100
|
-
expect(Bullet.
|
101
|
-
expect(Bullet.
|
96
|
+
it 'is deleted from the safelist successfully' do
|
97
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
98
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
99
|
+
Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
100
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
101
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|
@@ -129,7 +129,7 @@ if active_record?
|
|
129
129
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
130
130
|
end
|
131
131
|
|
132
|
-
it 'should detect unused preload with post =>
|
132
|
+
it 'should detect unused preload with post => comments, no category => posts' do
|
133
133
|
Category.includes(posts: :comments).each { |category| category.posts.map(&:name) }
|
134
134
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
135
135
|
expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)
|
@@ -202,7 +202,7 @@ if active_record?
|
|
202
202
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
203
203
|
end
|
204
204
|
|
205
|
-
it 'should detect preload with post =>
|
205
|
+
it 'should detect preload with post => comments' do
|
206
206
|
Post.first.comments.map(&:name)
|
207
207
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
208
208
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
@@ -401,6 +401,15 @@ if active_record?
|
|
401
401
|
end
|
402
402
|
|
403
403
|
describe Bullet::Detector::Association, 'has_and_belongs_to_many' do
|
404
|
+
context 'posts <=> deals' do
|
405
|
+
it 'should detect preload associations with join tables that have identifier' do
|
406
|
+
Post.includes(:deals).each { |post| post.deals.map(&:name) }
|
407
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
408
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
409
|
+
|
410
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
411
|
+
end
|
412
|
+
end
|
404
413
|
context 'students <=> teachers' do
|
405
414
|
it 'should detect non preload associations' do
|
406
415
|
Student.all.each { |student| student.teachers.map(&:name) }
|
@@ -442,6 +451,16 @@ if active_record?
|
|
442
451
|
expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Student, :teachers)
|
443
452
|
end
|
444
453
|
end
|
454
|
+
|
455
|
+
context 'user => roles' do
|
456
|
+
it 'should detect preload associations' do
|
457
|
+
User.first.roles.includes(:resource).each { |role| role.resource }
|
458
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
459
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
460
|
+
|
461
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
462
|
+
end
|
463
|
+
end
|
445
464
|
end
|
446
465
|
|
447
466
|
describe Bullet::Detector::Association, 'has_many :through' do
|
@@ -729,9 +748,9 @@ if active_record?
|
|
729
748
|
end
|
730
749
|
end
|
731
750
|
|
732
|
-
context '
|
733
|
-
before { Bullet.
|
734
|
-
after { Bullet.
|
751
|
+
context 'add n plus one query to safelist' do
|
752
|
+
before { Bullet.add_safelist type: :n_plus_one_query, class_name: 'Post', association: :comments }
|
753
|
+
after { Bullet.clear_safelist }
|
735
754
|
|
736
755
|
it 'should not detect n plus one query' do
|
737
756
|
Post.all.each { |post| post.comments.map(&:name) }
|
@@ -750,9 +769,9 @@ if active_record?
|
|
750
769
|
end
|
751
770
|
end
|
752
771
|
|
753
|
-
context '
|
754
|
-
before { Bullet.
|
755
|
-
after { Bullet.
|
772
|
+
context 'add unused eager loading to safelist' do
|
773
|
+
before { Bullet.add_safelist type: :unused_eager_loading, class_name: 'Post', association: :comments }
|
774
|
+
after { Bullet.clear_safelist }
|
756
775
|
|
757
776
|
it 'should not detect unused eager loading' do
|
758
777
|
Post.includes(:comments).map(&:name)
|
@@ -28,7 +28,7 @@ if !mongoid? && active_record?
|
|
28
28
|
expect(Bullet.collected_counter_cache_notifications).to be_empty
|
29
29
|
end
|
30
30
|
|
31
|
-
if
|
31
|
+
if ActiveRecord::VERSION::MAJOR > 4
|
32
32
|
it 'should not need counter cache for has_many through' do
|
33
33
|
Client.all.each { |client| client.firms.size }
|
34
34
|
expect(Bullet.collected_counter_cache_notifications).to be_empty
|
@@ -55,9 +55,9 @@ if !mongoid? && active_record?
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
context '
|
59
|
-
before { Bullet.
|
60
|
-
after { Bullet.
|
58
|
+
context 'safelist' do
|
59
|
+
before { Bullet.add_safelist type: :counter_cache, class_name: 'Country', association: :cities }
|
60
|
+
after { Bullet.clear_safelist }
|
61
61
|
|
62
62
|
it 'should not detect counter cache' do
|
63
63
|
Country.all.each { |country| country.cities.size }
|
@@ -118,7 +118,7 @@ if mongoid?
|
|
118
118
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
119
119
|
end
|
120
120
|
|
121
|
-
it 'should detect preload with post =>
|
121
|
+
it 'should detect preload with post => comments' do
|
122
122
|
Mongoid::Post.first.comments.map(&:name)
|
123
123
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
124
124
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
data/spec/models/deal.rb
ADDED
data/spec/models/folder.rb
CHANGED
data/spec/models/group.rb
CHANGED