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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ee8344236feb882d0359202c851abb8887b6b3bb31186f2b37329a41fbc3e96
4
- data.tar.gz: 9d1aea1b67a6777c56b2548e93614ee051d5799e8caf9ecb0d48663a6a7b5bd8
3
+ metadata.gz: 0f47e147f5df074217ee7a65123f54a1918f98bde162c7d4bf1f76bffc9f15a6
4
+ data.tar.gz: e05e48b96a5e68bfbcac63f4dce4601d717ec1a32ee83a5c45aad738e1bfc48b
5
5
  SHA512:
6
- metadata.gz: f603fb2540b20dd0e5d5e519de308b9a264d264bbea6db9d7399c25d6912e053aa243cb877b98cfa123f3eca9142bc0fec1bf9b4ff41f411efd6a895f616916b
7
- data.tar.gz: 1e000b7bb96d2479108bb67bc6ac6d4efdc3f337f76f42e8227d1b8b9394a7d38d09df0104ceb3ed7b6e9a98b570dc0f32d3eeff85277490e4ee0c552b577f5b
6
+ metadata.gz: 87bf095754befedb8f1137ae039191807baa1f1bcadb09c923b4f1a6abd385e73e4143e50077358a8f7155e3bf367edb1b405e3b0b260c8de5dd572699b7bafc
7
+ data.tar.gz: fd692ae67a1695ee4598b24d3cd030a7f091deb42f30df0f92085b3c54f765960c0b5eabaace7b306fadf8539b514fe77076cff825c27a292c5e36f56916646b
@@ -9,9 +9,9 @@ name: CI
9
9
 
10
10
  on:
11
11
  push:
12
- branches: [ master ]
12
+ branches: [ main ]
13
13
  pull_request:
14
- branches: [ master ]
14
+ branches: [ main ]
15
15
 
16
16
  jobs:
17
17
  test_rails_4:
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
- if refl.through_reflection?
189
- refl = refl.through_reflection while refl.through_reflection?
187
+ if refl.through_reflection?
188
+ refl = refl.through_reflection while refl.through_reflection?
190
189
 
191
- Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
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
- if reflection.through_reflection != through_reflection
162
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
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
- if reflection.through_reflection != through_reflection
189
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
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
- if reflection.through_reflection != through_reflection
189
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
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
- if reflection.through_reflection != through_reflection
192
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
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
- return if objects.map(&:bullet_primary_key_value).compact.empty?
38
- return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
39
-
40
- Bullet.debug(
41
- 'Detector::NPlusOneQuery#add_possible_objects',
42
- "objects: #{objects.map(&:bullet_key).join(', ')}"
43
- )
44
- objects.each { |object| possible_objects.add object.bullet_key }
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
- response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
24
- response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
25
- if Bullet.add_footer && !Bullet.skip_http_headers
26
- response_body = append_to_html_body(response_body, xhr_script)
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
- "<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module Registry
5
+ class CallStack < Base
6
+ # remembers found association backtrace
7
+ def add(key)
8
+ @registry[key] = Thread.current.backtrace
9
+ end
10
+ end
11
+ end
12
+ end
@@ -5,5 +5,6 @@ module Bullet
5
5
  autoload :Base, 'bullet/registry/base'
6
6
  autoload :Object, 'bullet/registry/object'
7
7
  autoload :Association, 'bullet/registry/association'
8
+ autoload :CallStack, 'bullet/registry/call_stack'
8
9
  end
9
10
  end
@@ -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
- def caller_in_project
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
- caller.select { |caller_path| yield caller_path }
59
- else
60
- caller_locations.select { |location| yield location }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '7.0.4'
4
+ VERSION = '7.0.7'
5
5
  end
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
- app.middleware.use Bullet::Rack
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
@@ -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.includes(:groups).each { |firm| firm.groups.map(&:name) }
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
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: 2022-11-28 00:00:00.000000000 Z
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.3.22
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