bullet 7.0.3 → 7.0.5
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 +2 -2
- data/CHANGELOG.md +13 -0
- data/MIT-LICENSE +1 -1
- data/README.md +4 -2
- data/lib/bullet/active_record5.rb +10 -8
- data/lib/bullet/active_record52.rb +9 -7
- data/lib/bullet/active_record60.rb +8 -6
- data/lib/bullet/active_record61.rb +8 -6
- data/lib/bullet/active_record70.rb +15 -6
- data/lib/bullet/detector/association.rb +8 -0
- data/lib/bullet/detector/n_plus_one_query.rb +20 -9
- data/lib/bullet/detector/unused_eager_loading.rb +1 -1
- data/lib/bullet/mongoid7x.rb +8 -10
- data/lib/bullet/rack.rb +38 -6
- data/lib/bullet/registry/call_stack.rb +12 -0
- data/lib/bullet/registry.rb +1 -0
- data/lib/bullet/stack_trace_filter.rb +10 -8
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +6 -1
- data/spec/bullet/detector/n_plus_one_query_spec.rb +0 -32
- data/spec/bullet/detector/unused_eager_loading_spec.rb +5 -0
- data/spec/bullet/rack_spec.rb +18 -1
- data/spec/bullet/stack_trace_filter_spec.rb +26 -0
- data/spec/integration/active_record/association_spec.rb +31 -2
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f67f5d4add512b0606aa27cf26e9d2a628d6eb7dc58078d4c3eaeca4d3bfccf7
|
|
4
|
+
data.tar.gz: aa55cdd15e0f585a679595b98b858194c3d217acedf9ef0db70085cc74b7710c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 919bb619038a60d8657bc09f2c34949934c933de190ae0d3ed2025e4599e6cbe284b132b2ed92ea6f94dfbc06566ba56aa522684107cab1aa3a1386a431a527e
|
|
7
|
+
data.tar.gz: 3682a26c40d5a6b6559f7d82324ad3236ba91db28a99f1416117a5147986e26cdc75c643a0a0a58891f781ba5511a44d90124f5f7a637ce43b2d3c3e31baf3a5
|
data/.github/workflows/main.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## Next Release
|
|
2
2
|
|
|
3
|
+
## 7.0.5 (01/01/2023)
|
|
4
|
+
|
|
5
|
+
* Fix n+1 false positives in AR 7.0
|
|
6
|
+
* Fix eager_load nested has_many :through false positives
|
|
7
|
+
* Respect Content-Security-Policy nonces
|
|
8
|
+
* Added CallStacks support for avoid eager loading
|
|
9
|
+
* Iterate fewer times over objects
|
|
10
|
+
|
|
11
|
+
## 7.0.4 (11/28/2022)
|
|
12
|
+
|
|
13
|
+
* Fix `eager_load` `has_many :through` false positives
|
|
14
|
+
* mongoid7x: add dynamic methods
|
|
15
|
+
|
|
3
16
|
## 7.0.3 (08/13/2022)
|
|
4
17
|
|
|
5
18
|
* Replace `Array()` with `Array.wrap()`
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -222,7 +222,7 @@ end
|
|
|
222
222
|
|
|
223
223
|
### Work with sinatra
|
|
224
224
|
|
|
225
|
-
Configure and use `Bullet::Rack
|
|
225
|
+
Configure and use `Bullet::Rack`.
|
|
226
226
|
|
|
227
227
|
```ruby
|
|
228
228
|
configure :development do
|
|
@@ -232,6 +232,8 @@ configure :development do
|
|
|
232
232
|
end
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
+
If your application generates a Content-Security-Policy via a separate middleware, ensure that `Bullet::Rack` is loaded _before_ that middleware.
|
|
236
|
+
|
|
235
237
|
### Run in tests
|
|
236
238
|
|
|
237
239
|
First you need to enable Bullet in test environment.
|
|
@@ -285,7 +287,7 @@ $ rails g scaffold comment name:string post_id:integer
|
|
|
285
287
|
$ bundle exec rake db:migrate
|
|
286
288
|
```
|
|
287
289
|
|
|
288
|
-
2\. Change `app/
|
|
290
|
+
2\. Change `app/models/post.rb` and `app/models/comment.rb`
|
|
289
291
|
|
|
290
292
|
```ruby
|
|
291
293
|
class Post < ActiveRecord::Base
|
|
@@ -177,16 +177,18 @@ module Bullet
|
|
|
177
177
|
if Bullet.start?
|
|
178
178
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
179
179
|
refl = reflection.through_reflection
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
180
|
+
association = owner.association(refl.name)
|
|
181
|
+
if association.loaded?
|
|
182
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
|
|
183
|
+
Array.wrap(association.target).each do |through_record|
|
|
184
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
185
|
+
end
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
if refl.through_reflection?
|
|
188
|
+
refl = refl.through_reflection while refl.through_reflection?
|
|
188
189
|
|
|
189
|
-
|
|
190
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
|
|
191
|
+
end
|
|
190
192
|
end
|
|
191
193
|
end
|
|
192
194
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
|
@@ -150,14 +150,16 @@ module Bullet
|
|
|
150
150
|
|
|
151
151
|
if Bullet.start?
|
|
152
152
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
association = owner.association(reflection.through_reflection.name)
|
|
154
|
+
if association.loaded?
|
|
155
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
156
|
+
Array.wrap(association.target).each do |through_record|
|
|
157
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
158
|
+
end
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
if reflection.through_reflection != through_reflection
|
|
161
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
162
|
+
end
|
|
161
163
|
end
|
|
162
164
|
end
|
|
163
165
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
|
@@ -177,14 +177,16 @@ module Bullet
|
|
|
177
177
|
|
|
178
178
|
if Bullet.start?
|
|
179
179
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
180
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
181
180
|
association = owner.association(reflection.through_reflection.name)
|
|
182
|
-
|
|
183
|
-
Bullet::Detector::NPlusOneQuery.call_association(
|
|
184
|
-
|
|
181
|
+
if association.loaded?
|
|
182
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
183
|
+
Array.wrap(association.target).each do |through_record|
|
|
184
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
185
|
+
end
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
if reflection.through_reflection != through_reflection
|
|
188
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
189
|
+
end
|
|
188
190
|
end
|
|
189
191
|
end
|
|
190
192
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
|
@@ -177,14 +177,16 @@ module Bullet
|
|
|
177
177
|
|
|
178
178
|
if Bullet.start?
|
|
179
179
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
180
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
181
180
|
association = owner.association(reflection.through_reflection.name)
|
|
182
|
-
|
|
183
|
-
Bullet::Detector::NPlusOneQuery.call_association(
|
|
184
|
-
|
|
181
|
+
if association.loaded?
|
|
182
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
183
|
+
Array.wrap(association.target).each do |through_record|
|
|
184
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
185
|
+
end
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
if reflection.through_reflection != through_reflection
|
|
188
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
189
|
+
end
|
|
188
190
|
end
|
|
189
191
|
end
|
|
190
192
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
|
@@ -170,6 +170,13 @@ module Bullet
|
|
|
170
170
|
end
|
|
171
171
|
super
|
|
172
172
|
end
|
|
173
|
+
|
|
174
|
+
def inversed_from_queries(record)
|
|
175
|
+
if Bullet.start? && inversable?(record)
|
|
176
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
|
177
|
+
end
|
|
178
|
+
super
|
|
179
|
+
end
|
|
173
180
|
end
|
|
174
181
|
)
|
|
175
182
|
|
|
@@ -180,14 +187,16 @@ module Bullet
|
|
|
180
187
|
|
|
181
188
|
if Bullet.start?
|
|
182
189
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
183
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
184
190
|
association = owner.association(reflection.through_reflection.name)
|
|
185
|
-
|
|
186
|
-
Bullet::Detector::NPlusOneQuery.call_association(
|
|
187
|
-
|
|
191
|
+
if association.loaded?
|
|
192
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
193
|
+
Array.wrap(association.target).each do |through_record|
|
|
194
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
195
|
+
end
|
|
188
196
|
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
if reflection.through_reflection != through_reflection
|
|
198
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
199
|
+
end
|
|
191
200
|
end
|
|
192
201
|
end
|
|
193
202
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
@@ -13,6 +13,7 @@ module Bullet
|
|
|
13
13
|
'Detector::Association#add_object_associations',
|
|
14
14
|
"object: #{object.bullet_key}, associations: #{associations}"
|
|
15
15
|
)
|
|
16
|
+
call_stacks.add(object.bullet_key)
|
|
16
17
|
object_associations.add(object.bullet_key, associations)
|
|
17
18
|
end
|
|
18
19
|
|
|
@@ -25,6 +26,7 @@ module Bullet
|
|
|
25
26
|
'Detector::Association#add_call_object_associations',
|
|
26
27
|
"object: #{object.bullet_key}, associations: #{associations}"
|
|
27
28
|
)
|
|
29
|
+
call_stacks.add(object.bullet_key)
|
|
28
30
|
call_object_associations.add(object.bullet_key, associations)
|
|
29
31
|
end
|
|
30
32
|
|
|
@@ -76,6 +78,12 @@ module Bullet
|
|
|
76
78
|
def eager_loadings
|
|
77
79
|
Thread.current[:bullet_eager_loadings]
|
|
78
80
|
end
|
|
81
|
+
|
|
82
|
+
# cal_stacks keeps stacktraces where querie-objects were called from.
|
|
83
|
+
# e.g. { 'Object:111' => [SomeProject/app/controllers/...] }
|
|
84
|
+
def call_stacks
|
|
85
|
+
Thread.current[:bullet_call_stacks]
|
|
86
|
+
end
|
|
79
87
|
end
|
|
80
88
|
end
|
|
81
89
|
end
|
|
@@ -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
|
|
|
@@ -34,14 +34,25 @@ module Bullet
|
|
|
34
34
|
return unless Bullet.n_plus_one_query_enable?
|
|
35
35
|
|
|
36
36
|
objects = Array.wrap(object_or_objects)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
45
56
|
end
|
|
46
57
|
|
|
47
58
|
def add_impossible_object(object)
|
|
@@ -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
|
|
data/lib/bullet/mongoid7x.rb
CHANGED
|
@@ -4,22 +4,20 @@ 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)
|
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
|
|
@@ -20,11 +22,15 @@ module Bullet
|
|
|
20
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
|
-
|
|
26
|
-
response_body = append_to_html_body(response_body,
|
|
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
|
|
27
32
|
end
|
|
33
|
+
|
|
28
34
|
headers['Content-Length'] = response_body.bytesize.to_s
|
|
29
35
|
elsif !Bullet.skip_http_headers
|
|
30
36
|
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
|
@@ -118,8 +124,34 @@ module Bullet
|
|
|
118
124
|
end
|
|
119
125
|
|
|
120
126
|
# Make footer work for XHR requests by appending data to the footer
|
|
121
|
-
def xhr_script
|
|
122
|
-
|
|
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
|
|
123
155
|
end
|
|
124
156
|
end
|
|
125
157
|
end
|
data/lib/bullet/registry.rb
CHANGED
|
@@ -6,10 +6,11 @@ module Bullet
|
|
|
6
6
|
VENDOR_PATH = '/vendor'
|
|
7
7
|
IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# @param bullet_key[String] - use this to get stored call stack from call_stacks object.
|
|
10
|
+
def caller_in_project(bullet_key = nil)
|
|
10
11
|
vendor_root = Bullet.app_root + VENDOR_PATH
|
|
11
12
|
bundler_path = Bundler.bundle_path.to_s
|
|
12
|
-
select_caller_locations do |location|
|
|
13
|
+
select_caller_locations(bullet_key) do |location|
|
|
13
14
|
caller_path = location_as_path(location)
|
|
14
15
|
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
|
15
16
|
!caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
|
|
@@ -50,15 +51,16 @@ module Bullet
|
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def location_as_path(location)
|
|
54
|
+
return location if location.is_a?(String)
|
|
55
|
+
|
|
53
56
|
IS_RUBY_19 ? location : location.absolute_path.to_s
|
|
54
57
|
end
|
|
55
58
|
|
|
56
|
-
def select_caller_locations
|
|
57
|
-
if IS_RUBY_19
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
end
|
|
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 }
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -23,7 +23,11 @@ module Bullet
|
|
|
23
23
|
if defined?(Rails::Railtie)
|
|
24
24
|
class BulletRailtie < Rails::Railtie
|
|
25
25
|
initializer 'bullet.configure_rails_initialization' do |app|
|
|
26
|
-
|
|
26
|
+
if defined?(ActionDispatch::ContentSecurityPolicy::Middleware)
|
|
27
|
+
app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, Bullet::Rack
|
|
28
|
+
else
|
|
29
|
+
app.middleware.use Bullet::Rack
|
|
30
|
+
end
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
end
|
|
@@ -144,6 +148,7 @@ module Bullet
|
|
|
144
148
|
Thread.current[:bullet_impossible_objects] = Bullet::Registry::Object.new
|
|
145
149
|
Thread.current[:bullet_inversed_objects] = Bullet::Registry::Base.new
|
|
146
150
|
Thread.current[:bullet_eager_loadings] = Bullet::Registry::Association.new
|
|
151
|
+
Thread.current[:bullet_call_stacks] = Bullet::Registry::CallStack.new
|
|
147
152
|
|
|
148
153
|
Thread.current[:bullet_counter_possible_objects] ||= Bullet::Registry::Object.new
|
|
149
154
|
Thread.current[:bullet_counter_impossible_objects] ||= Bullet::Registry::Object.new
|
|
@@ -127,38 +127,6 @@ module Bullet
|
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
context '.caller_in_project' do
|
|
131
|
-
it 'should include only paths that are in the project' do
|
|
132
|
-
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
|
133
|
-
not_in_project = OpenStruct.new(absolute_path: '/def/def.rb')
|
|
134
|
-
|
|
135
|
-
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, not_in_project])
|
|
136
|
-
expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
|
|
137
|
-
expect(NPlusOneQuery).to receive(:create_notification).with([in_project], 'Post', :association)
|
|
138
|
-
NPlusOneQuery.call_association(@post, :association)
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
context 'stacktrace_includes' do
|
|
142
|
-
before { Bullet.stacktrace_includes = ['def', /xyz/] }
|
|
143
|
-
after { Bullet.stacktrace_includes = nil }
|
|
144
|
-
|
|
145
|
-
it 'should include paths that are in the stacktrace_include list' do
|
|
146
|
-
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
|
147
|
-
included_gems = [OpenStruct.new(absolute_path: '/def/def.rb'), OpenStruct.new(absolute_path: 'xyz/xyz.rb')]
|
|
148
|
-
excluded_gem = OpenStruct.new(absolute_path: '/ghi/ghi.rb')
|
|
149
|
-
|
|
150
|
-
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, *included_gems, excluded_gem])
|
|
151
|
-
expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
|
|
152
|
-
expect(NPlusOneQuery).to receive(:create_notification).with(
|
|
153
|
-
[in_project, *included_gems],
|
|
154
|
-
'Post',
|
|
155
|
-
:association
|
|
156
|
-
)
|
|
157
|
-
NPlusOneQuery.call_association(@post, :association)
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
130
|
context '.add_possible_objects' do
|
|
163
131
|
it 'should add possible objects' do
|
|
164
132
|
NPlusOneQuery.add_possible_objects([@post, @post2])
|
|
@@ -65,6 +65,11 @@ module Bullet
|
|
|
65
65
|
expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
|
|
66
66
|
UnusedEagerLoading.check_unused_preload_associations
|
|
67
67
|
end
|
|
68
|
+
|
|
69
|
+
it 'should create call stack for notification' do
|
|
70
|
+
UnusedEagerLoading.add_object_associations(@post, :association)
|
|
71
|
+
expect(UnusedEagerLoading.send(:call_stacks).registry).not_to be_empty
|
|
72
|
+
end
|
|
68
73
|
end
|
|
69
74
|
|
|
70
75
|
context '.add_eager_loadings' do
|
data/spec/bullet/rack_spec.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Bullet
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
it 'should be true if no response body' do
|
|
53
|
-
response = double
|
|
53
|
+
response = double
|
|
54
54
|
expect(middleware).to be_empty(response)
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -129,6 +129,23 @@ module Bullet
|
|
|
129
129
|
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
it 'should include CSP nonce in inline script if console_enabled and a CSP is applied' do
|
|
133
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
|
134
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
|
135
|
+
allow(middleware).to receive(:xhr_script).and_call_original
|
|
136
|
+
|
|
137
|
+
nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='
|
|
138
|
+
app.headers = {
|
|
139
|
+
'Content-Type' => 'text/html',
|
|
140
|
+
'Content-Security-Policy' => "default-src 'self' https:; script-src 'self' https: 'nonce-#{nonce}'"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
|
144
|
+
|
|
145
|
+
size = 56 + middleware.send(:footer_note).length + middleware.send(:xhr_script, nonce).length
|
|
146
|
+
expect(headers['Content-Length']).to eq(size.to_s)
|
|
147
|
+
end
|
|
148
|
+
|
|
132
149
|
it 'should change response body for html safe string if console_enabled is true' do
|
|
133
150
|
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
|
134
151
|
app.response =
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
module Bullet
|
|
6
|
+
RSpec.describe StackTraceFilter do
|
|
7
|
+
let(:dummy_class) { Class.new { extend StackTraceFilter } }
|
|
8
|
+
let(:root_path) { Dir.pwd }
|
|
9
|
+
let(:bundler_path) { Bundler.bundle_path }
|
|
10
|
+
|
|
11
|
+
describe '#caller_in_project' do
|
|
12
|
+
it 'gets the caller in the project' do
|
|
13
|
+
expect(dummy_class).to receive(:call_stacks).and_return({
|
|
14
|
+
'Post:1' => [
|
|
15
|
+
File.join(root_path, 'lib/bullet.rb'),
|
|
16
|
+
File.join(root_path, 'vendor/uniform_notifier.rb'),
|
|
17
|
+
File.join(bundler_path, 'rack.rb')
|
|
18
|
+
]
|
|
19
|
+
})
|
|
20
|
+
expect(dummy_class.caller_in_project('Post:1')).to eq([
|
|
21
|
+
File.join(root_path, 'lib/bullet.rb')
|
|
22
|
+
])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -58,6 +58,19 @@ if active_record?
|
|
|
58
58
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
it 'should detect non preload comment => post with inverse_of from a query' do
|
|
62
|
+
Post.first.comments.find_each do |comment|
|
|
63
|
+
comment.name
|
|
64
|
+
comment.post.name
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
68
|
+
expect(Post.first.comments.count).not_to eq(0)
|
|
69
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
70
|
+
|
|
71
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
72
|
+
end
|
|
73
|
+
|
|
61
74
|
it 'should detect non preload post => comments with empty?' do
|
|
62
75
|
Post.all.each { |post| post.comments.empty? }
|
|
63
76
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
@@ -474,7 +487,15 @@ if active_record?
|
|
|
474
487
|
end
|
|
475
488
|
|
|
476
489
|
it 'should detect preload associations' do
|
|
477
|
-
Firm.
|
|
490
|
+
Firm.preload(:clients).each { |firm| firm.clients.map(&:name) }
|
|
491
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
492
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
493
|
+
|
|
494
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
it 'should detect eager load association' do
|
|
498
|
+
Firm.eager_load(:clients).each { |firm| firm.clients.map(&:name) }
|
|
478
499
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
479
500
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
480
501
|
|
|
@@ -508,7 +529,15 @@ if active_record?
|
|
|
508
529
|
end
|
|
509
530
|
|
|
510
531
|
it 'should detect preload associations' do
|
|
511
|
-
Firm.
|
|
532
|
+
Firm.preload(:groups).each { |firm| firm.groups.map(&:name) }
|
|
533
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
534
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
535
|
+
|
|
536
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
it 'should detect eager load associations' do
|
|
540
|
+
Firm.eager_load(:groups).each { |firm| firm.groups.map(&:name) }
|
|
512
541
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
513
542
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
514
543
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bullet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.0.
|
|
4
|
+
version: 7.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Huang
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-01-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -104,6 +104,7 @@ files:
|
|
|
104
104
|
- lib/bullet/registry.rb
|
|
105
105
|
- lib/bullet/registry/association.rb
|
|
106
106
|
- lib/bullet/registry/base.rb
|
|
107
|
+
- lib/bullet/registry/call_stack.rb
|
|
107
108
|
- lib/bullet/registry/object.rb
|
|
108
109
|
- lib/bullet/stack_trace_filter.rb
|
|
109
110
|
- lib/bullet/version.rb
|
|
@@ -126,6 +127,7 @@ files:
|
|
|
126
127
|
- spec/bullet/registry/association_spec.rb
|
|
127
128
|
- spec/bullet/registry/base_spec.rb
|
|
128
129
|
- spec/bullet/registry/object_spec.rb
|
|
130
|
+
- spec/bullet/stack_trace_filter_spec.rb
|
|
129
131
|
- spec/bullet_spec.rb
|
|
130
132
|
- spec/integration/active_record/association_spec.rb
|
|
131
133
|
- spec/integration/counter_cache_spec.rb
|
|
@@ -195,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
195
197
|
- !ruby/object:Gem::Version
|
|
196
198
|
version: 1.3.6
|
|
197
199
|
requirements: []
|
|
198
|
-
rubygems_version: 3.
|
|
200
|
+
rubygems_version: 3.4.1
|
|
199
201
|
signing_key:
|
|
200
202
|
specification_version: 4
|
|
201
203
|
summary: help to kill N+1 queries and unused eager loading.
|
|
@@ -216,6 +218,7 @@ test_files:
|
|
|
216
218
|
- spec/bullet/registry/association_spec.rb
|
|
217
219
|
- spec/bullet/registry/base_spec.rb
|
|
218
220
|
- spec/bullet/registry/object_spec.rb
|
|
221
|
+
- spec/bullet/stack_trace_filter_spec.rb
|
|
219
222
|
- spec/bullet_spec.rb
|
|
220
223
|
- spec/integration/active_record/association_spec.rb
|
|
221
224
|
- spec/integration/counter_cache_spec.rb
|