bullet 7.0.4 → 7.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ee8344236feb882d0359202c851abb8887b6b3bb31186f2b37329a41fbc3e96
4
- data.tar.gz: 9d1aea1b67a6777c56b2548e93614ee051d5799e8caf9ecb0d48663a6a7b5bd8
3
+ metadata.gz: f67f5d4add512b0606aa27cf26e9d2a628d6eb7dc58078d4c3eaeca4d3bfccf7
4
+ data.tar.gz: aa55cdd15e0f585a679595b98b858194c3d217acedf9ef0db70085cc74b7710c
5
5
  SHA512:
6
- metadata.gz: f603fb2540b20dd0e5d5e519de308b9a264d264bbea6db9d7399c25d6912e053aa243cb877b98cfa123f3eca9142bc0fec1bf9b4ff41f411efd6a895f616916b
7
- data.tar.gz: 1e000b7bb96d2479108bb67bc6ac6d4efdc3f337f76f42e8227d1b8b9394a7d38d09df0104ceb3ed7b6e9a98b570dc0f32d3eeff85277490e4ee0c552b577f5b
6
+ metadata.gz: 919bb619038a60d8657bc09f2c34949934c933de190ae0d3ed2025e4599e6cbe284b132b2ed92ea6f94dfbc06566ba56aa522684107cab1aa3a1386a431a527e
7
+ data.tar.gz: 3682a26c40d5a6b6559f7d82324ad3236ba91db28a99f1416117a5147986e26cdc75c643a0a0a58891f781ba5511a44d90124f5f7a637ce43b2d3c3e31baf3a5
@@ -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,13 @@
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
+
3
11
  ## 7.0.4 (11/28/2022)
4
12
 
5
13
  * 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.5'
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)
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.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: 2022-11-28 00:00:00.000000000 Z
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.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