bullet 6.0.2 → 7.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +82 -0
- data/CHANGELOG.md +52 -0
- data/Gemfile.rails-6.0 +1 -1
- data/Gemfile.rails-6.1 +15 -0
- data/Gemfile.rails-7.0 +10 -0
- data/README.md +39 -25
- data/lib/bullet/active_job.rb +1 -3
- data/lib/bullet/active_record4.rb +9 -23
- data/lib/bullet/active_record41.rb +8 -19
- data/lib/bullet/active_record42.rb +9 -16
- data/lib/bullet/active_record5.rb +188 -170
- data/lib/bullet/active_record52.rb +182 -162
- data/lib/bullet/active_record60.rb +191 -178
- data/lib/bullet/active_record61.rb +272 -0
- data/lib/bullet/active_record70.rb +275 -0
- data/lib/bullet/bullet_xhr.js +18 -23
- data/lib/bullet/dependency.rb +52 -34
- data/lib/bullet/detector/association.rb +24 -18
- data/lib/bullet/detector/counter_cache.rb +12 -8
- data/lib/bullet/detector/n_plus_one_query.rb +20 -10
- data/lib/bullet/detector/unused_eager_loading.rb +7 -4
- data/lib/bullet/mongoid4x.rb +3 -7
- data/lib/bullet/mongoid5x.rb +3 -7
- data/lib/bullet/mongoid6x.rb +3 -7
- data/lib/bullet/mongoid7x.rb +26 -13
- data/lib/bullet/notification/base.rb +14 -18
- data/lib/bullet/notification/n_plus_one_query.rb +2 -4
- data/lib/bullet/notification/unused_eager_loading.rb +2 -4
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +28 -17
- data/lib/bullet/stack_trace_filter.rb +10 -17
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +52 -42
- data/lib/generators/bullet/install_generator.rb +22 -23
- data/perf/benchmark.rb +11 -14
- data/spec/bullet/detector/counter_cache_spec.rb +6 -6
- data/spec/bullet/detector/n_plus_one_query_spec.rb +8 -4
- data/spec/bullet/detector/unused_eager_loading_spec.rb +25 -8
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/notification/base_spec.rb +5 -7
- 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 +154 -13
- data/spec/bullet/registry/association_spec.rb +2 -2
- data/spec/bullet/registry/base_spec.rb +1 -1
- data/spec/bullet_spec.rb +25 -44
- data/spec/integration/active_record/association_spec.rb +104 -130
- data/spec/integration/counter_cache_spec.rb +14 -34
- data/spec/integration/mongoid/association_spec.rb +19 -33
- data/spec/models/attachment.rb +5 -0
- data/spec/models/deal.rb +5 -0
- 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/spec_helper.rb +4 -10
- data/spec/support/bullet_ext.rb +8 -9
- data/spec/support/mongo_seed.rb +3 -16
- data/spec/support/sqlite_seed.rb +38 -0
- data/test.sh +2 -0
- metadata +17 -7
- data/.travis.yml +0 -31
@@ -7,7 +7,11 @@ module Bullet
|
|
7
7
|
describe UnusedEagerLoading do
|
8
8
|
subject { UnusedEagerLoading.new([''], Post, %i[comments votes], 'path') }
|
9
9
|
|
10
|
-
it
|
10
|
+
it do
|
11
|
+
expect(subject.body).to eq(
|
12
|
+
" Post => [:comments, :votes]\n Remove from your query: .includes([:comments, :votes])"
|
13
|
+
)
|
14
|
+
end
|
11
15
|
it { expect(subject.title).to eq('AVOID eager loading in path') }
|
12
16
|
end
|
13
17
|
end
|
data/spec/bullet/rack_spec.rb
CHANGED
@@ -31,12 +31,6 @@ module Bullet
|
|
31
31
|
response = double(body: '<html><head></head><body></body></html>')
|
32
32
|
expect(middleware).not_to be_html_request(headers, response)
|
33
33
|
end
|
34
|
-
|
35
|
-
it "should be false if response body doesn't contain html tag" do
|
36
|
-
headers = { 'Content-Type' => 'text/html' }
|
37
|
-
response = double(body: '<div>Partial</div>')
|
38
|
-
expect(middleware).not_to be_html_request(headers, response)
|
39
|
-
end
|
40
34
|
end
|
41
35
|
|
42
36
|
context 'empty?' do
|
@@ -54,6 +48,11 @@ module Bullet
|
|
54
48
|
response = double(body: '')
|
55
49
|
expect(middleware).to be_empty(response)
|
56
50
|
end
|
51
|
+
|
52
|
+
it 'should be true if no response body' do
|
53
|
+
response = double()
|
54
|
+
expect(middleware).to be_empty(response)
|
55
|
+
end
|
57
56
|
end
|
58
57
|
|
59
58
|
context '#call' do
|
@@ -67,12 +66,12 @@ module Bullet
|
|
67
66
|
|
68
67
|
it 'should change response body if notification is active' do
|
69
68
|
expect(Bullet).to receive(:notification?).and_return(true)
|
69
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
70
70
|
expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
71
|
-
expect(middleware).to receive(:xhr_script).and_return('')
|
72
71
|
expect(Bullet).to receive(:perform_out_of_channel_notifications)
|
73
|
-
|
72
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
74
73
|
expect(headers['Content-Length']).to eq('56')
|
75
|
-
expect(response).to eq([
|
74
|
+
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
76
75
|
end
|
77
76
|
|
78
77
|
it 'should set the right Content-Length if response body contains accents' do
|
@@ -80,9 +79,137 @@ module Bullet
|
|
80
79
|
response.body = '<html><head></head><body>é</body></html>'
|
81
80
|
app.response = response
|
82
81
|
expect(Bullet).to receive(:notification?).and_return(true)
|
82
|
+
allow(Bullet).to receive(:console_enabled?).and_return(true)
|
83
83
|
expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
84
|
-
|
85
|
-
expect(headers['Content-Length']).to eq(
|
84
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
85
|
+
expect(headers['Content-Length']).to eq('58')
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with injection notifiers' do
|
89
|
+
before do
|
90
|
+
expect(Bullet).to receive(:notification?).and_return(true)
|
91
|
+
allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
92
|
+
allow(middleware).to receive(:xhr_script).and_return('<script></script>')
|
93
|
+
allow(middleware).to receive(:footer_note).and_return('footer')
|
94
|
+
expect(Bullet).to receive(:perform_out_of_channel_notifications)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should change response body if add_footer is true' do
|
98
|
+
expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
|
99
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
100
|
+
|
101
|
+
expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
|
102
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should change response body for html safe string if add_footer is true' do
|
106
|
+
expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
|
107
|
+
app.response =
|
108
|
+
Support::ResponseDouble.new.tap do |response|
|
109
|
+
response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
|
110
|
+
end
|
111
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
112
|
+
|
113
|
+
expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
|
114
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should add the footer-text header for non-html requests when add_footer is true' do
|
118
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
119
|
+
allow(Bullet).to receive(:footer_info).and_return(['footer text'])
|
120
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
121
|
+
_, headers, _response = middleware.call({})
|
122
|
+
expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should change response body if console_enabled is true' do
|
126
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
127
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
128
|
+
expect(headers['Content-Length']).to eq('56')
|
129
|
+
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should change response body for html safe string if console_enabled is true' do
|
133
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
134
|
+
app.response =
|
135
|
+
Support::ResponseDouble.new.tap do |response|
|
136
|
+
response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
|
137
|
+
end
|
138
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
139
|
+
expect(headers['Content-Length']).to eq('56')
|
140
|
+
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'should add headers for non-html requests when console_enabled is true' do
|
144
|
+
allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
|
145
|
+
allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
|
146
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
147
|
+
_, headers, _response = middleware.call({})
|
148
|
+
expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
|
149
|
+
end
|
150
|
+
|
151
|
+
it "shouldn't change response body unnecessarily" do
|
152
|
+
expected_response = Support::ResponseDouble.new 'Actual body'
|
153
|
+
app.response = expected_response
|
154
|
+
_, _, response = middleware.call({})
|
155
|
+
expect(response).to eq(expected_response)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "shouldn't add headers unnecessarily" do
|
159
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
160
|
+
_, headers, _response = middleware.call({})
|
161
|
+
expect(headers).not_to include('X-bullet-footer-text')
|
162
|
+
expect(headers).not_to include('X-bullet-console-text')
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'when skip_http_headers is enabled' do
|
166
|
+
before do
|
167
|
+
allow(Bullet).to receive(:skip_http_headers).and_return(true)
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should include the footer but not the xhr script tag if add_footer is true' do
|
171
|
+
expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
172
|
+
_, headers, response = middleware.call({})
|
173
|
+
|
174
|
+
expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
|
175
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should not include the xhr script tag if console_enabled is true' do
|
179
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
180
|
+
_, headers, response = middleware.call({})
|
181
|
+
expect(headers['Content-Length']).to eq('56')
|
182
|
+
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should not add the footer-text header for non-html requests when add_footer is true' do
|
186
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
187
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
188
|
+
_, headers, _response = middleware.call({})
|
189
|
+
expect(headers).not_to include('X-bullet-footer-text')
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should not add headers for non-html requests when console_enabled is true' do
|
193
|
+
allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
|
194
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
195
|
+
_, headers, _response = middleware.call({})
|
196
|
+
expect(headers).not_to include('X-bullet-console-text')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context 'when skip_html_injection is enabled' do
|
202
|
+
it 'should not try to inject html' do
|
203
|
+
expected_response = Support::ResponseDouble.new 'Actual body'
|
204
|
+
app.response = expected_response
|
205
|
+
allow(Bullet).to receive(:notification?).and_return(true)
|
206
|
+
allow(Bullet).to receive(:skip_html_injection?).and_return(true)
|
207
|
+
expect(Bullet).to receive(:gather_inline_notifications).never
|
208
|
+
expect(middleware).to receive(:xhr_script).never
|
209
|
+
expect(Bullet).to receive(:perform_out_of_channel_notifications)
|
210
|
+
_, _, response = middleware.call('Content-Type' => 'text/html')
|
211
|
+
expect(response).to eq(expected_response)
|
212
|
+
end
|
86
213
|
end
|
87
214
|
end
|
88
215
|
|
@@ -98,8 +225,8 @@ module Bullet
|
|
98
225
|
|
99
226
|
context '#set_header' do
|
100
227
|
it 'should truncate headers to under 8kb' do
|
101
|
-
long_header = ['a' *
|
102
|
-
expected_res = (['a' *
|
228
|
+
long_header = ['a' * 1_024] * 10
|
229
|
+
expected_res = (['a' * 1_024] * 7).to_json
|
103
230
|
expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
|
104
231
|
end
|
105
232
|
end
|
@@ -133,6 +260,20 @@ module Bullet
|
|
133
260
|
expect(middleware.response_body(response)).to eq body_string
|
134
261
|
end
|
135
262
|
end
|
263
|
+
|
264
|
+
begin
|
265
|
+
require 'rack/files'
|
266
|
+
|
267
|
+
context 'when `response` is a Rack::Files::Iterator' do
|
268
|
+
let(:response) { instance_double(::Rack::Files::Iterator) }
|
269
|
+
before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
|
270
|
+
|
271
|
+
it 'should return nil' do
|
272
|
+
expect(middleware.response_body(response)).to be_nil
|
273
|
+
end
|
274
|
+
end
|
275
|
+
rescue LoadError
|
276
|
+
end
|
136
277
|
end
|
137
278
|
end
|
138
279
|
end
|
@@ -16,11 +16,11 @@ module Bullet
|
|
16
16
|
|
17
17
|
context '#similarly_associated' do
|
18
18
|
it 'should return similarly associated keys' do
|
19
|
-
expect(subject.similarly_associated('key1', Set.new([
|
19
|
+
expect(subject.similarly_associated('key1', Set.new(%w[value]))).to eq(%w[key1 key2])
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'should return empty if key does not exist' do
|
23
|
-
expect(subject.similarly_associated('key3', Set.new([
|
23
|
+
expect(subject.similarly_associated('key3', Set.new(%w[value]))).to be_empty
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
data/spec/bullet_spec.rb
CHANGED
@@ -17,9 +17,7 @@ describe Bullet, focused: true do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
context 'disable Bullet' do
|
20
|
-
before
|
21
|
-
Bullet.enable = false
|
22
|
-
end
|
20
|
+
before { Bullet.enable = false }
|
23
21
|
|
24
22
|
it 'should be disabled' do
|
25
23
|
expect(subject).to_not be_enable
|
@@ -27,8 +25,8 @@ describe Bullet, focused: true do
|
|
27
25
|
|
28
26
|
context 'enable Bullet again without patching again the orms' do
|
29
27
|
before do
|
30
|
-
expect(Bullet::Mongoid).not_to receive(:enable) if defined?
|
31
|
-
expect(Bullet::ActiveRecord).not_to receive(:enable) if defined?
|
28
|
+
expect(Bullet::Mongoid).not_to receive(:enable) if defined?(Bullet::Mongoid)
|
29
|
+
expect(Bullet::ActiveRecord).not_to receive(:enable) if defined?(Bullet::ActiveRecord)
|
32
30
|
Bullet.enable = true
|
33
31
|
end
|
34
32
|
|
@@ -42,9 +40,7 @@ describe Bullet, focused: true do
|
|
42
40
|
|
43
41
|
describe '#start?' do
|
44
42
|
context 'when bullet is disabled' do
|
45
|
-
before(:each)
|
46
|
-
Bullet.enable = false
|
47
|
-
end
|
43
|
+
before(:each) { Bullet.enable = false }
|
48
44
|
|
49
45
|
it 'should not be started' do
|
50
46
|
expect(Bullet).not_to be_start
|
@@ -53,28 +49,19 @@ describe Bullet, focused: true do
|
|
53
49
|
end
|
54
50
|
|
55
51
|
describe '#debug' do
|
56
|
-
before(:each)
|
57
|
-
$stdout = StringIO.new
|
58
|
-
end
|
52
|
+
before(:each) { $stdout = StringIO.new }
|
59
53
|
|
60
|
-
after(:each)
|
61
|
-
$stdout = STDOUT
|
62
|
-
end
|
54
|
+
after(:each) { $stdout = STDOUT }
|
63
55
|
|
64
56
|
context 'when debug is enabled' do
|
65
|
-
before(:each)
|
66
|
-
ENV['BULLET_DEBUG'] = 'true'
|
67
|
-
end
|
57
|
+
before(:each) { ENV['BULLET_DEBUG'] = 'true' }
|
68
58
|
|
69
|
-
after(:each)
|
70
|
-
ENV['BULLET_DEBUG'] = 'false'
|
71
|
-
end
|
59
|
+
after(:each) { ENV['BULLET_DEBUG'] = 'false' }
|
72
60
|
|
73
61
|
it 'should output debug information' do
|
74
62
|
Bullet.debug('debug_message', 'this is helpful information')
|
75
63
|
|
76
|
-
expect($stdout.string)
|
77
|
-
.to eq("[Bullet][debug_message] this is helpful information\n")
|
64
|
+
expect($stdout.string).to eq("[Bullet][debug_message] this is helpful information\n")
|
78
65
|
end
|
79
66
|
end
|
80
67
|
|
@@ -87,31 +74,31 @@ describe Bullet, focused: true do
|
|
87
74
|
end
|
88
75
|
end
|
89
76
|
|
90
|
-
describe '#
|
77
|
+
describe '#add_safelist' do
|
91
78
|
context "for 'special' class names" do
|
92
|
-
it 'is added to the
|
93
|
-
Bullet.
|
94
|
-
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
|
95
82
|
end
|
96
83
|
end
|
97
84
|
end
|
98
85
|
|
99
|
-
describe '#
|
86
|
+
describe '#delete_safelist' do
|
100
87
|
context "for 'special' class names" do
|
101
|
-
it 'is deleted from the
|
102
|
-
Bullet.
|
103
|
-
Bullet.
|
104
|
-
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({})
|
105
92
|
end
|
106
93
|
end
|
107
94
|
|
108
95
|
context 'when exists multiple definitions' do
|
109
|
-
it 'is deleted from the
|
110
|
-
Bullet.
|
111
|
-
Bullet.
|
112
|
-
Bullet.
|
113
|
-
expect(Bullet.
|
114
|
-
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
|
115
102
|
end
|
116
103
|
end
|
117
104
|
end
|
@@ -125,13 +112,7 @@ describe Bullet, focused: true do
|
|
125
112
|
end
|
126
113
|
|
127
114
|
context 'when called with Rack environment hash' do
|
128
|
-
let(:env) {
|
129
|
-
{
|
130
|
-
'REQUEST_METHOD' => 'GET',
|
131
|
-
'PATH_INFO' => '/path',
|
132
|
-
'QUERY_STRING' => 'foo=bar'
|
133
|
-
}
|
134
|
-
}
|
115
|
+
let(:env) { { 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/path', 'QUERY_STRING' => 'foo=bar' } }
|
135
116
|
|
136
117
|
context "when env['REQUEST_URI'] is nil" do
|
137
118
|
before { env['REQUEST_URI'] = nil }
|