bullet 7.0.4 → 7.0.7
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 +16 -0
- data/README.md +3 -1
- data/lib/bullet/active_record5.rb +4 -4
- data/lib/bullet/active_record52.rb +3 -3
- data/lib/bullet/active_record60.rb +3 -3
- data/lib/bullet/active_record61.rb +3 -3
- data/lib/bullet/active_record70.rb +10 -3
- 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/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 +22 -1
- 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: 0f47e147f5df074217ee7a65123f54a1918f98bde162c7d4bf1f76bffc9f15a6
|
4
|
+
data.tar.gz: e05e48b96a5e68bfbcac63f4dce4601d717ec1a32ee83a5c45aad738e1bfc48b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87bf095754befedb8f1137ae039191807baa1f1bcadb09c923b4f1a6abd385e73e4143e50077358a8f7155e3bf367edb1b405e3b0b260c8de5dd572699b7bafc
|
7
|
+
data.tar.gz: fd692ae67a1695ee4598b24d3cd030a7f091deb42f30df0f92085b3c54f765960c0b5eabaace7b306fadf8539b514fe77076cff825c27a292c5e36f56916646b
|
data/.github/workflows/main.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## Next Release
|
2
2
|
|
3
|
+
## 7.0.7 (03/01/2023)
|
4
|
+
|
5
|
+
* Check `Rails.application.config.content_security_policy` before insert `Bullet::Rack`
|
6
|
+
|
7
|
+
## 7.0.6 (03/01/2023)
|
8
|
+
|
9
|
+
* Better way to check if `ActionDispatch::ContentSecurityPolicy::Middleware` exists
|
10
|
+
|
11
|
+
## 7.0.5 (01/01/2023)
|
12
|
+
|
13
|
+
* Fix n+1 false positives in AR 7.0
|
14
|
+
* Fix eager_load nested has_many :through false positives
|
15
|
+
* Respect Content-Security-Policy nonces
|
16
|
+
* Added CallStacks support for avoid eager loading
|
17
|
+
* Iterate fewer times over objects
|
18
|
+
|
3
19
|
## 7.0.4 (11/28/2022)
|
4
20
|
|
5
21
|
* Fix `eager_load` `has_many :through` false positives
|
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.
|
@@ -183,12 +183,12 @@ module Bullet
|
|
183
183
|
Array.wrap(association.target).each do |through_record|
|
184
184
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
185
185
|
end
|
186
|
-
end
|
187
186
|
|
188
|
-
|
189
|
-
|
187
|
+
if refl.through_reflection?
|
188
|
+
refl = refl.through_reflection while refl.through_reflection?
|
190
189
|
|
191
|
-
|
190
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
|
191
|
+
end
|
192
192
|
end
|
193
193
|
end
|
194
194
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
@@ -156,10 +156,10 @@ module Bullet
|
|
156
156
|
Array.wrap(association.target).each do |through_record|
|
157
157
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
158
158
|
end
|
159
|
-
end
|
160
159
|
|
161
|
-
|
162
|
-
|
160
|
+
if reflection.through_reflection != through_reflection
|
161
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
162
|
+
end
|
163
163
|
end
|
164
164
|
end
|
165
165
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
@@ -183,10 +183,10 @@ module Bullet
|
|
183
183
|
Array.wrap(association.target).each do |through_record|
|
184
184
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
185
185
|
end
|
186
|
-
end
|
187
186
|
|
188
|
-
|
189
|
-
|
187
|
+
if reflection.through_reflection != through_reflection
|
188
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
189
|
+
end
|
190
190
|
end
|
191
191
|
end
|
192
192
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
@@ -183,10 +183,10 @@ module Bullet
|
|
183
183
|
Array.wrap(association.target).each do |through_record|
|
184
184
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
185
185
|
end
|
186
|
-
end
|
187
186
|
|
188
|
-
|
189
|
-
|
187
|
+
if reflection.through_reflection != through_reflection
|
188
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
189
|
+
end
|
190
190
|
end
|
191
191
|
end
|
192
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
|
|
@@ -186,10 +193,10 @@ module Bullet
|
|
186
193
|
Array.wrap(association.target).each do |through_record|
|
187
194
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
188
195
|
end
|
189
|
-
end
|
190
196
|
|
191
|
-
|
192
|
-
|
197
|
+
if reflection.through_reflection != through_reflection
|
198
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
199
|
+
end
|
193
200
|
end
|
194
201
|
end
|
195
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/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) && Rails.application.config.content_security_policy
|
27
|
+
app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, Bullet::Rack
|
28
|
+
else
|
29
|
+
app.middleware.use Bullet::Rack
|
30
|
+
end
|
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
|
@@ -516,7 +529,15 @@ if active_record?
|
|
516
529
|
end
|
517
530
|
|
518
531
|
it 'should detect preload associations' do
|
519
|
-
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) }
|
520
541
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
521
542
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
522
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.7
|
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-03 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
|