bullet 8.0.8 → 8.1.1

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: ac2296e419c6f32f6ed2e2dbe92a344884d1197b8287bb6c67783a1587c31c72
4
- data.tar.gz: 523f4b66265e7ca8b4d67b19ecd13a547f7ad55acbdbd8190ec1e48a1cb4993e
3
+ metadata.gz: 4f10d7c7ed8bf9cb6e6b3f0d8d2757bfa71a66d8c4f90da0ce708cfce06eddb8
4
+ data.tar.gz: 4986e5b6390339bd6034a598a5f24678dd98c1db907f07de3a2aac1cef4501fd
5
5
  SHA512:
6
- metadata.gz: 463b978de4299a651821f4bf66ce219ba3def30f32d8fbb790086d10b869526578c2f5b441a1c0c5d368c03906e833a86b5ed88cda1a2f983e5769c04bf7efb2
7
- data.tar.gz: 5ab230e714c07790210516fbb760007fbbb49875a4021950b8e5d2c8ab77e132240f632ee50a62ee25dc58f629663f6d9802bee626d870d456284e3d4130ed3e
6
+ metadata.gz: 323f11d8aee1ba3175eb02795aafb800ad1262f4da84333698fa2f73011b5f9912ad7c38fe37f347ac1b492e90cff87db322433fc6ef55262ab1ecf2e6cc02d7
7
+ data.tar.gz: e44277b7412ff505bb3c21047f8eeff5774f367375989192ef27367102eae91f04eb6625f89110160181690ab440051ae6dc2db39d0b5eb89b30fe47a164eab9
data/CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
- ## Next Release
1
+ ## 8.1.1 (04/23/2026)
2
+
3
+ * Fix ActiveRecord 8.1 patch-level method signature compatibility; test against Rails 8.1.3.
4
+ * Handle string associations in safelist for Action Text
5
+ * Enhance N+1 query detection by including caller stack in association calls
6
+ * Update external links in README.md
7
+
8
+ ## 8.1.0 (10/23/2025)
9
+
10
+ * Make `get_relation` private
11
+ * Support Rails 8.1
2
12
 
3
13
  ## 8.0.8 (05/30/2025)
4
14
 
data/README.md CHANGED
@@ -17,10 +17,9 @@ If you use activerecord 3.x, please use bullet < 5.5.0
17
17
 
18
18
  ## External Introduction
19
19
 
20
- * [http://railscasts.com/episodes/372-bullet](http://railscasts.com/episodes/372-bullet)
21
- * [http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009](http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009)
22
- * [http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1](http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1)
23
20
  * [https://rubyonrails.org/2009/10/22/community-highlights](https://rubyonrails.org/2009/10/22/community-highlights)
21
+ * [https://www.shakacode.com/blog/eliminate-N-1-queries-with-bullet/](https://www.shakacode.com/blog/eliminate-N-1-queries-with-bullet/)
22
+ * [https://reinteractive.com/articles/ruby-on-rails-community/elevating-your-code-quality-bullet-gem-and-query-prevention-in-testing](https://reinteractive.com/articles/ruby-on-rails-community/elevating-your-code-quality-bullet-gem-and-query-prevention-in-testing)
24
23
 
25
24
  ## Install
26
25
 
@@ -71,10 +70,14 @@ config.after_initialize do
71
70
  Bullet.rollbar = true
72
71
  Bullet.add_footer = true
73
72
  Bullet.skip_html_injection = false
73
+ Bullet.skip_http_headers = false
74
74
  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
75
75
  Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
76
76
  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
77
77
  Bullet.opentelemetry = true
78
+ Bullet.raise = false
79
+ Bullet.always_append_html_body = false
80
+ Bullet.skip_user_in_notification = false
78
81
  end
79
82
  ```
80
83
 
@@ -82,6 +85,7 @@ The notifier of Bullet is a wrap of [uniform_notifier](https://github.com/flyerh
82
85
 
83
86
  The code above will enable all of the Bullet notification systems:
84
87
  * `Bullet.enable`: enable Bullet gem, otherwise do nothing
88
+ * `Bullet.sentry`: add notifications to sentry
85
89
  * `Bullet.alert`: pop up a JavaScript alert in the browser
86
90
  * `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)
87
91
  * `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
@@ -89,10 +93,9 @@ The code above will enable all of the Bullet notification systems:
89
93
  * `Bullet.rails_logger`: add warnings directly to the Rails log
90
94
  * `Bullet.honeybadger`: add notifications to Honeybadger
91
95
  * `Bullet.bugsnag`: add notifications to bugsnag
92
- * `Bullet.airbrake`: add notifications to airbrake
93
96
  * `Bullet.appsignal`: add notifications to AppSignal
97
+ * `Bullet.airbrake`: add notifications to airbrake
94
98
  * `Bullet.rollbar`: add notifications to rollbar
95
- * `Bullet.sentry`: add notifications to sentry
96
99
  * `Bullet.add_footer`: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
97
100
  * `Bullet.skip_html_injection`: prevents Bullet from injecting code into the returned HTML. This must be false for receiving alerts, showing the footer or console logging.
98
101
  * `Bullet.skip_http_headers`: don't add headers to API requests, and remove the javascript that relies on them. Note that this prevents bullet from logging warnings to the browser console or updating the footer.
@@ -255,11 +255,10 @@ module Bullet
255
255
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
256
256
  Module.new do
257
257
  def empty?
258
- result = super
259
258
  if Bullet.start? && !reflection.has_cached_counter?
260
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
259
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
261
260
  end
262
- result
261
+ super
263
262
  end
264
263
 
265
264
  def count_records
@@ -237,11 +237,10 @@ module Bullet
237
237
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
238
238
  Module.new do
239
239
  def empty?
240
- result = super
241
240
  if Bullet.start? && !reflection.has_cached_counter?
242
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
241
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
243
242
  end
244
- result
243
+ super
245
244
  end
246
245
 
247
246
  def count_records
@@ -216,13 +216,13 @@ module Bullet
216
216
 
217
217
  def empty?
218
218
  if Bullet.start? && !reflection.has_cached_counter?
219
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
219
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
220
220
  end
221
221
  super
222
222
  end
223
223
 
224
224
  def include?(object)
225
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
225
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
226
226
  super
227
227
  end
228
228
  end
@@ -264,11 +264,10 @@ module Bullet
264
264
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
265
265
  Module.new do
266
266
  def empty?
267
- result = super
268
267
  if Bullet.start? && !reflection.has_cached_counter?
269
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
268
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
270
269
  end
271
- result
270
+ super
272
271
  end
273
272
 
274
273
  def count_records
@@ -291,7 +290,8 @@ module Bullet
291
290
  )
292
291
  Bullet::Detector::NPlusOneQuery.call_association(
293
292
  proxy_association.owner,
294
- proxy_association.reflection.name
293
+ proxy_association.reflection.name,
294
+ caller_locations
295
295
  )
296
296
  end
297
297
  super(column_name)
@@ -216,13 +216,13 @@ module Bullet
216
216
 
217
217
  def empty?
218
218
  if Bullet.start? && !reflection.has_cached_counter?
219
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
219
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
220
220
  end
221
221
  super
222
222
  end
223
223
 
224
224
  def include?(object)
225
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
225
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
226
226
  super
227
227
  end
228
228
  end
@@ -264,11 +264,10 @@ module Bullet
264
264
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
265
265
  Module.new do
266
266
  def empty?
267
- result = super
268
267
  if Bullet.start? && !reflection.has_cached_counter?
269
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
268
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
270
269
  end
271
- result
270
+ super
272
271
  end
273
272
 
274
273
  def count_records
@@ -291,7 +290,8 @@ module Bullet
291
290
  )
292
291
  Bullet::Detector::NPlusOneQuery.call_association(
293
292
  proxy_association.owner,
294
- proxy_association.reflection.name
293
+ proxy_association.reflection.name,
294
+ caller_locations
295
295
  )
296
296
  end
297
297
  super(column_name)
@@ -232,13 +232,13 @@ module Bullet
232
232
 
233
233
  def empty?
234
234
  if Bullet.start? && !reflection.has_cached_counter?
235
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
235
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
236
236
  end
237
237
  super
238
238
  end
239
239
 
240
240
  def include?(object)
241
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
241
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
242
242
  super
243
243
  end
244
244
  end
@@ -280,11 +280,10 @@ module Bullet
280
280
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
281
281
  Module.new do
282
282
  def empty?
283
- result = super
284
283
  if Bullet.start? && !reflection.has_cached_counter?
285
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
284
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
286
285
  end
287
- result
286
+ super
288
287
  end
289
288
 
290
289
  def count_records
@@ -307,7 +306,8 @@ module Bullet
307
306
  )
308
307
  Bullet::Detector::NPlusOneQuery.call_association(
309
308
  proxy_association.owner,
310
- proxy_association.reflection.name
309
+ proxy_association.reflection.name,
310
+ caller_locations
311
311
  )
312
312
  end
313
313
  super(column_name)
@@ -232,13 +232,13 @@ module Bullet
232
232
 
233
233
  def empty?
234
234
  if Bullet.start? && !reflection.has_cached_counter?
235
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
235
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
236
236
  end
237
237
  super
238
238
  end
239
239
 
240
240
  def include?(object)
241
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
241
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
242
242
  super
243
243
  end
244
244
  end
@@ -280,11 +280,10 @@ module Bullet
280
280
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
281
281
  Module.new do
282
282
  def empty?
283
- result = super
284
283
  if Bullet.start? && !reflection.has_cached_counter?
285
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
284
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
286
285
  end
287
- result
286
+ super
288
287
  end
289
288
 
290
289
  def count_records
@@ -307,7 +306,8 @@ module Bullet
307
306
  )
308
307
  Bullet::Detector::NPlusOneQuery.call_association(
309
308
  proxy_association.owner,
310
- proxy_association.reflection.name
309
+ proxy_association.reflection.name,
310
+ caller_locations
311
311
  )
312
312
  end
313
313
  super(column_name)
@@ -232,13 +232,13 @@ module Bullet
232
232
 
233
233
  def empty?
234
234
  if Bullet.start? && !reflection.has_cached_counter?
235
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
235
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
236
236
  end
237
237
  super
238
238
  end
239
239
 
240
240
  def include?(object)
241
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
241
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
242
242
  super
243
243
  end
244
244
  end
@@ -280,11 +280,10 @@ module Bullet
280
280
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
281
281
  Module.new do
282
282
  def empty?
283
- result = super
284
283
  if Bullet.start? && !reflection.has_cached_counter?
285
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
284
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
286
285
  end
287
- result
286
+ super
288
287
  end
289
288
 
290
289
  def count_records
@@ -307,7 +306,8 @@ module Bullet
307
306
  )
308
307
  Bullet::Detector::NPlusOneQuery.call_association(
309
308
  proxy_association.owner,
310
- proxy_association.reflection.name
309
+ proxy_association.reflection.name,
310
+ caller_locations
311
311
  )
312
312
  end
313
313
  super(column_name)
@@ -232,13 +232,13 @@ module Bullet
232
232
 
233
233
  def empty?
234
234
  if Bullet.start? && !reflection.has_cached_counter?
235
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
235
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
236
236
  end
237
237
  super
238
238
  end
239
239
 
240
240
  def include?(object)
241
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
241
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
242
242
  super
243
243
  end
244
244
  end
@@ -280,11 +280,10 @@ module Bullet
280
280
  ::ActiveRecord::Associations::HasManyAssociation.prepend(
281
281
  Module.new do
282
282
  def empty?
283
- result = super
284
283
  if Bullet.start? && !reflection.has_cached_counter?
285
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
284
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
286
285
  end
287
- result
286
+ super
288
287
  end
289
288
 
290
289
  def count_records
@@ -307,7 +306,8 @@ module Bullet
307
306
  )
308
307
  Bullet::Detector::NPlusOneQuery.call_association(
309
308
  proxy_association.owner,
310
- proxy_association.reflection.name
309
+ proxy_association.reflection.name,
310
+ caller_locations
311
311
  )
312
312
  end
313
313
  super(column_name)
@@ -0,0 +1,321 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module SaveWithBulletSupport
5
+ def _create_record(*)
6
+ super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
8
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
9
+ yield(self) if block_given?
10
+ end
11
+ end
12
+ end
13
+
14
+ module ActiveRecord
15
+ def self.enable
16
+ require 'active_record'
17
+ ::ActiveRecord::Base.extend(
18
+ Module.new do
19
+ def find_by_sql(*args, **kwargs, &block)
20
+ result = super(*args, **kwargs, &block)
21
+ if Bullet.start?
22
+ if result.is_a? Array
23
+ if result.size > 1
24
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
25
+ Bullet::Detector::CounterCache.add_possible_objects(result)
26
+ elsif result.size == 1
27
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
28
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
29
+ end
30
+ elsif result.is_a? ::ActiveRecord::Base
31
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
32
+ Bullet::Detector::CounterCache.add_impossible_object(result)
33
+ end
34
+ end
35
+ result
36
+ end
37
+ end
38
+ )
39
+
40
+ ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
41
+
42
+ ::ActiveRecord::Relation.prepend(
43
+ Module.new do
44
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
45
+ # if select only one object, then the only one object has impossible to cause N+1 query.
46
+ def records
47
+ result = super
48
+ if Bullet.start?
49
+ if result.first.class.name !~ /^HABTM_/
50
+ if result.size > 1
51
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
52
+ Bullet::Detector::CounterCache.add_possible_objects(result)
53
+ elsif result.size == 1
54
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
55
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
56
+ end
57
+ end
58
+ end
59
+ result
60
+ end
61
+ end
62
+ )
63
+
64
+ ::ActiveRecord::Associations::Preloader::Batch.prepend(
65
+ Module.new do
66
+ def call
67
+ if Bullet.start?
68
+ @preloaders.each do |preloader|
69
+ preloader.records.each { |record|
70
+ Bullet::Detector::Association.add_object_associations(record, preloader.associations)
71
+ }
72
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
73
+ end
74
+ end
75
+ super
76
+ end
77
+ end
78
+ )
79
+
80
+ ::ActiveRecord::Associations::Preloader::Branch.prepend(
81
+ Module.new do
82
+ def preloaders_for_reflection(reflection, reflection_records)
83
+ if Bullet.start?
84
+ reflection_records.compact!
85
+ if reflection_records.first.class.name !~ /^HABTM_/
86
+ reflection_records.each { |record|
87
+ Bullet::Detector::Association.add_object_associations(record, reflection.name)
88
+ }
89
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
90
+ end
91
+ end
92
+ super
93
+ end
94
+ end
95
+ )
96
+
97
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
98
+ Module.new do
99
+ def source_preloaders
100
+ if Bullet.start? && !defined?(@source_preloaders)
101
+ preloaders = super
102
+ preloaders.each do |preloader|
103
+ reflection_name = preloader.send(:reflection).name
104
+ preloader.send(:owners).each do |owner|
105
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
106
+ end
107
+ end
108
+ else
109
+ super
110
+ end
111
+ end
112
+ end
113
+ )
114
+
115
+ ::ActiveRecord::Associations::JoinDependency.prepend(
116
+ Module.new do
117
+ def instantiate(*args, **kwargs, &block)
118
+ @bullet_eager_loadings = {}
119
+ records = super(*args, **kwargs, &block)
120
+
121
+ if Bullet.start?
122
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
123
+ objects = eager_loadings_hash.keys
124
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
125
+ objects,
126
+ eager_loadings_hash[objects.first].to_a
127
+ )
128
+ end
129
+ end
130
+ records
131
+ end
132
+
133
+ def construct(*args, **kwargs, &block)
134
+ ar_parent, parent, row = args
135
+ if Bullet.start?
136
+ unless ar_parent.nil?
137
+ parent.children.each do |node|
138
+ key = aliases.column_alias(node, node.primary_key)
139
+ id = row[key]
140
+ next unless id.nil?
141
+
142
+ associations = [node.reflection.name]
143
+ if node.reflection.through_reflection?
144
+ associations << node.reflection.through_reflection.name
145
+ end
146
+ associations.each do |association|
147
+ Bullet::Detector::Association.add_object_associations(ar_parent, association)
148
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
149
+ @bullet_eager_loadings[ar_parent.class] ||= {}
150
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
151
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << association
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ super(*args, **kwargs, &block)
158
+ end
159
+
160
+ # call join associations
161
+ def construct_model(*args, **kwargs, &block)
162
+ record, node = args
163
+ result = super(*args, **kwargs, &block)
164
+
165
+ if Bullet.start?
166
+ associations = [node.reflection.name]
167
+ if node.reflection.through_reflection?
168
+ associations << node.reflection.through_reflection.name
169
+ end
170
+ associations.each do |association|
171
+ Bullet::Detector::Association.add_object_associations(record, association)
172
+ Bullet::Detector::NPlusOneQuery.call_association(record, association)
173
+ @bullet_eager_loadings[record.class] ||= {}
174
+ @bullet_eager_loadings[record.class][record] ||= Set.new
175
+ @bullet_eager_loadings[record.class][record] << association
176
+ end
177
+ end
178
+
179
+ result
180
+ end
181
+ end
182
+ )
183
+
184
+ ::ActiveRecord::Associations::Association.prepend(
185
+ Module.new do
186
+ def inversed_from(record)
187
+ if Bullet.start?
188
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
189
+ end
190
+ super
191
+ end
192
+
193
+ def inversed_from_queries(record)
194
+ if Bullet.start? && inversable?(record)
195
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
196
+ end
197
+ super
198
+ end
199
+ end
200
+ )
201
+
202
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
203
+ Module.new do
204
+ def load_target
205
+ records = super
206
+
207
+ if Bullet.start?
208
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
209
+ association = owner.association(reflection.through_reflection.name)
210
+ if association.loaded?
211
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
212
+ Array.wrap(association.target).each do |through_record|
213
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
214
+ end
215
+
216
+ if reflection.through_reflection != through_reflection
217
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
218
+ end
219
+ end
220
+ end
221
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
222
+ if records.first.class.name !~ /^HABTM_/
223
+ if records.size > 1
224
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
225
+ Bullet::Detector::CounterCache.add_possible_objects(records)
226
+ elsif records.size == 1
227
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
228
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
229
+ end
230
+ end
231
+ end
232
+ records
233
+ end
234
+
235
+ def empty?
236
+ if Bullet.start? && !reflection.has_cached_counter?
237
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
238
+ end
239
+ super
240
+ end
241
+
242
+ def include?(object)
243
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
244
+ super
245
+ end
246
+ end
247
+ )
248
+
249
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
250
+ Module.new do
251
+ # call has_one and belongs_to associations
252
+ def reader
253
+ result = super
254
+
255
+ if Bullet.start?
256
+ if owner.class.name !~ /^HABTM_/
257
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
258
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
259
+ association = owner.association(reflection.through_reflection.name)
260
+ Array.wrap(association.target).each do |through_record|
261
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
262
+ end
263
+
264
+ if reflection.through_reflection != through_reflection
265
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
266
+ end
267
+ end
268
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
269
+
270
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
271
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
272
+ else
273
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
274
+ end
275
+ end
276
+ end
277
+ result
278
+ end
279
+ end
280
+ )
281
+
282
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
283
+ Module.new do
284
+ def empty?
285
+ if Bullet.start? && !reflection.has_cached_counter?
286
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
287
+ end
288
+ super
289
+ end
290
+
291
+ def count_records
292
+ result = reflection.has_cached_counter?
293
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
294
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
295
+ end
296
+ super
297
+ end
298
+ end
299
+ )
300
+
301
+ ::ActiveRecord::Associations::CollectionProxy.prepend(
302
+ Module.new do
303
+ def count(column_name = nil)
304
+ if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
305
+ Bullet::Detector::CounterCache.add_counter_cache(
306
+ proxy_association.owner,
307
+ proxy_association.reflection.name
308
+ )
309
+ Bullet::Detector::NPlusOneQuery.call_association(
310
+ proxy_association.owner,
311
+ proxy_association.reflection.name,
312
+ caller_locations
313
+ )
314
+ end
315
+ super(column_name)
316
+ end
317
+ end
318
+ )
319
+ end
320
+ end
321
+ end
@@ -37,6 +37,8 @@ module Bullet
37
37
  'active_record72'
38
38
  elsif active_record80?
39
39
  'active_record80'
40
+ elsif active_record81?
41
+ 'active_record81'
40
42
  else
41
43
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
42
44
  end
@@ -132,6 +134,10 @@ module Bullet
132
134
  active_record8? && ::ActiveRecord::VERSION::MINOR == 0
133
135
  end
134
136
 
137
+ def active_record81?
138
+ active_record8? && ::ActiveRecord::VERSION::MINOR == 1
139
+ end
140
+
135
141
  def mongoid4x?
136
142
  mongoid? && ::Mongoid::VERSION =~ /\A4/
137
143
  end
@@ -13,13 +13,14 @@ module Bullet
13
13
  # first, it keeps this method call for object.association.
14
14
  # then, it checks if this associations call is unpreload.
15
15
  # if it is, keeps this unpreload associations and caller.
16
- def call_association(object, associations)
16
+ def call_association(object, associations, caller_stack = nil)
17
17
  return unless Bullet.start?
18
18
  return unless Bullet.n_plus_one_query_enable?
19
19
  return unless object.bullet_primary_key_value
20
20
  return if inversed_objects.include?(object.bullet_key, associations)
21
21
 
22
22
  add_call_object_associations(object, associations)
23
+ call_stacks.add(object.bullet_key, caller_stack) if caller_stack
23
24
 
24
25
  Bullet.debug(
25
26
  'Detector::NPlusOneQuery#call_association',
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Relations::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, metadata, object, reload = false)
50
52
  result = origin_get_relation(name, metadata, object, reload)
51
53
  Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Relations::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, metadata, object, reload = false)
50
52
  result = origin_get_relation(name, metadata, object, reload)
51
53
  Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Relations::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, metadata, object, reload = false)
50
52
  result = origin_get_relation(name, metadata, object, reload)
51
53
  Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
@@ -61,6 +61,8 @@ module Bullet
61
61
  ::Mongoid::Association::Accessors.class_eval do
62
62
  alias_method :origin_get_relation, :get_relation
63
63
 
64
+ private
65
+
64
66
  def get_relation(name, association, object, reload = false)
65
67
  result = origin_get_relation(name, association, object, reload)
66
68
  Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Association::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, association, object, reload = false)
50
52
  result = origin_get_relation(name, association, object, reload)
51
53
  unless association.embedded?
@@ -61,6 +61,8 @@ module Bullet
61
61
  ::Mongoid::Association::Accessors.class_eval do
62
62
  alias_method :origin_get_relation, :get_relation
63
63
 
64
+ private
65
+
64
66
  def get_relation(name, association, object, reload = false)
65
67
  result = origin_get_relation(name, association, object, reload)
66
68
  Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
@@ -4,8 +4,13 @@ module Bullet
4
4
  module Registry
5
5
  class CallStack < Base
6
6
  # remembers found association backtrace
7
- def add(key)
8
- @registry[key] ||= Thread.current.backtrace
7
+ # if backtrace is provided, it will be used and override any existing value
8
+ def add(key, backtrace = nil)
9
+ if backtrace
10
+ @registry[key] = backtrace
11
+ else
12
+ @registry[key] ||= Thread.current.backtrace
13
+ end
9
14
  end
10
15
  end
11
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '8.0.8'
4
+ VERSION = '8.1.1'
5
5
  end
data/lib/bullet.rb CHANGED
@@ -123,7 +123,7 @@ module Bullet
123
123
  end
124
124
 
125
125
  def get_safelist_associations(type, class_name)
126
- Array.wrap(@safelist[type][class_name])
126
+ Array.wrap(@safelist[type][class_name]).flat_map { |a| [a, a.to_s] }
127
127
  end
128
128
 
129
129
  def reset_safelist
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.8
4
+ version: 8.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
@@ -60,6 +60,7 @@ files:
60
60
  - lib/bullet/active_record71.rb
61
61
  - lib/bullet/active_record72.rb
62
62
  - lib/bullet/active_record80.rb
63
+ - lib/bullet/active_record81.rb
63
64
  - lib/bullet/bullet_xhr.js
64
65
  - lib/bullet/dependency.rb
65
66
  - lib/bullet/detector.rb
@@ -112,7 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
113
  - !ruby/object:Gem::Version
113
114
  version: 1.3.6
114
115
  requirements: []
115
- rubygems_version: 3.6.9
116
+ rubygems_version: 4.0.3
116
117
  specification_version: 4
117
118
  summary: help to kill N+1 queries and unused eager loading.
118
119
  test_files: []