bullet 5.9.0 → 7.0.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.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +72 -0
  4. data/Gemfile.rails-4.0 +1 -1
  5. data/Gemfile.rails-4.1 +1 -1
  6. data/Gemfile.rails-4.2 +1 -1
  7. data/Gemfile.rails-5.0 +1 -1
  8. data/Gemfile.rails-5.1 +1 -1
  9. data/Gemfile.rails-5.2 +1 -1
  10. data/Gemfile.rails-6.0 +15 -0
  11. data/Gemfile.rails-6.1 +15 -0
  12. data/Gemfile.rails-7.0 +10 -0
  13. data/MIT-LICENSE +1 -1
  14. data/README.md +59 -33
  15. data/lib/bullet/active_job.rb +13 -0
  16. data/lib/bullet/active_record4.rb +9 -32
  17. data/lib/bullet/active_record41.rb +8 -27
  18. data/lib/bullet/active_record42.rb +9 -24
  19. data/lib/bullet/active_record5.rb +190 -179
  20. data/lib/bullet/active_record52.rb +184 -169
  21. data/lib/bullet/active_record60.rb +274 -0
  22. data/lib/bullet/active_record61.rb +274 -0
  23. data/lib/bullet/active_record70.rb +277 -0
  24. data/lib/bullet/bullet_xhr.js +64 -0
  25. data/lib/bullet/dependency.rb +60 -36
  26. data/lib/bullet/detector/association.rb +26 -20
  27. data/lib/bullet/detector/counter_cache.rb +15 -11
  28. data/lib/bullet/detector/n_plus_one_query.rb +24 -14
  29. data/lib/bullet/detector/unused_eager_loading.rb +8 -5
  30. data/lib/bullet/ext/object.rb +4 -2
  31. data/lib/bullet/mongoid4x.rb +3 -7
  32. data/lib/bullet/mongoid5x.rb +3 -7
  33. data/lib/bullet/mongoid6x.rb +3 -7
  34. data/lib/bullet/mongoid7x.rb +34 -23
  35. data/lib/bullet/notification/base.rb +14 -18
  36. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  37. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  38. data/lib/bullet/notification.rb +2 -1
  39. data/lib/bullet/rack.rb +55 -27
  40. data/lib/bullet/stack_trace_filter.rb +11 -19
  41. data/lib/bullet/version.rb +1 -1
  42. data/lib/bullet.rb +68 -42
  43. data/lib/generators/bullet/install_generator.rb +22 -23
  44. data/perf/benchmark.rb +11 -14
  45. data/spec/bullet/detector/counter_cache_spec.rb +6 -6
  46. data/spec/bullet/detector/n_plus_one_query_spec.rb +8 -4
  47. data/spec/bullet/detector/unused_eager_loading_spec.rb +25 -8
  48. data/spec/bullet/ext/object_spec.rb +10 -5
  49. data/spec/bullet/notification/base_spec.rb +5 -7
  50. data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
  51. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  52. data/spec/bullet/rack_spec.rb +161 -11
  53. data/spec/bullet/registry/association_spec.rb +2 -2
  54. data/spec/bullet/registry/base_spec.rb +1 -1
  55. data/spec/bullet_spec.rb +25 -44
  56. data/spec/integration/active_record/association_spec.rb +115 -144
  57. data/spec/integration/counter_cache_spec.rb +14 -34
  58. data/spec/integration/mongoid/association_spec.rb +19 -33
  59. data/spec/models/attachment.rb +5 -0
  60. data/spec/models/deal.rb +5 -0
  61. data/spec/models/post.rb +2 -0
  62. data/spec/models/role.rb +7 -0
  63. data/spec/models/submission.rb +1 -0
  64. data/spec/models/user.rb +2 -0
  65. data/spec/spec_helper.rb +4 -10
  66. data/spec/support/bullet_ext.rb +8 -9
  67. data/spec/support/mongo_seed.rb +3 -16
  68. data/spec/support/sqlite_seed.rb +38 -0
  69. data/test.sh +3 -0
  70. metadata +21 -8
  71. data/.travis.yml +0 -12
@@ -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
@@ -45,15 +39,20 @@ module Bullet
45
39
  expect(middleware).not_to be_empty(response)
46
40
  end
47
41
 
48
- it 'should be true if response is not found' do
42
+ it 'should be false if response is not found' do
49
43
  response = ['Not Found']
50
- expect(middleware).to be_empty(response)
44
+ expect(middleware).not_to be_empty(response)
51
45
  end
52
46
 
53
47
  it 'should be true if response body is empty' do
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,11 +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
71
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
72
- status, headers, response = middleware.call('Content-Type' => 'text/html')
72
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
73
73
  expect(headers['Content-Length']).to eq('56')
74
- expect(response).to eq(['<html><head></head><body><bullet></bullet></body></html>'])
74
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
75
75
  end
76
76
 
77
77
  it 'should set the right Content-Length if response body contains accents' do
@@ -79,10 +79,138 @@ module Bullet
79
79
  response.body = '<html><head></head><body>é</body></html>'
80
80
  app.response = response
81
81
  expect(Bullet).to receive(:notification?).and_return(true)
82
+ allow(Bullet).to receive(:console_enabled?).and_return(true)
82
83
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
83
- status, headers, response = middleware.call('Content-Type' => 'text/html')
84
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
84
85
  expect(headers['Content-Length']).to eq('58')
85
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
213
+ end
86
214
  end
87
215
 
88
216
  context 'when Bullet is disabled' do
@@ -95,6 +223,14 @@ module Bullet
95
223
  end
96
224
  end
97
225
 
226
+ context '#set_header' do
227
+ it 'should truncate headers to under 8kb' do
228
+ long_header = ['a' * 1_024] * 10
229
+ expected_res = (['a' * 1_024] * 7).to_json
230
+ expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
231
+ end
232
+ end
233
+
98
234
  describe '#response_body' do
99
235
  let(:response) { double }
100
236
  let(:body_string) { '<html><body>My Body</body></html>' }
@@ -124,6 +260,20 @@ module Bullet
124
260
  expect(middleware.response_body(response)).to eq body_string
125
261
  end
126
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
127
277
  end
128
278
  end
129
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(['value']))).to eq(%w[key1 key2])
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(['value']))).to be_empty
23
+ expect(subject.similarly_associated('key3', Set.new(%w[value]))).to be_empty
24
24
  end
25
25
  end
26
26
  end
@@ -9,7 +9,7 @@ module Bullet
9
9
 
10
10
  context '#[]' do
11
11
  it 'should get value by key' do
12
- expect(subject['key']).to eq(Set.new(['value']))
12
+ expect(subject['key']).to eq(Set.new(%w[value]))
13
13
  end
14
14
  end
15
15
 
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 do
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? Bullet::Mongoid
31
- expect(Bullet::ActiveRecord).not_to receive(:enable) if defined? Bullet::ActiveRecord
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) do
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) do
57
- $stdout = StringIO.new
58
- end
52
+ before(:each) { $stdout = StringIO.new }
59
53
 
60
- after(:each) do
61
- $stdout = STDOUT
62
- end
54
+ after(:each) { $stdout = STDOUT }
63
55
 
64
56
  context 'when debug is enabled' do
65
- before(:each) do
66
- ENV['BULLET_DEBUG'] = 'true'
67
- end
57
+ before(:each) { ENV['BULLET_DEBUG'] = 'true' }
68
58
 
69
- after(:each) do
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 '#add_whitelist' do
77
+ describe '#add_safelist' do
91
78
  context "for 'special' class names" do
92
- it 'is added to the whitelist successfully' do
93
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
94
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
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 '#delete_whitelist' do
86
+ describe '#delete_safelist' do
100
87
  context "for 'special' class names" do
101
- it 'is deleted from the whitelist successfully' do
102
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
103
- Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
104
- expect(Bullet.whitelist[:n_plus_one_query]).to eq({})
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 whitelist successfully' do
110
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
111
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
112
- Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
113
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
114
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
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 }