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 +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +8 -5
- data/lib/bullet/active_record5.rb +2 -3
- data/lib/bullet/active_record52.rb +2 -3
- data/lib/bullet/active_record60.rb +6 -6
- data/lib/bullet/active_record61.rb +6 -6
- data/lib/bullet/active_record70.rb +6 -6
- data/lib/bullet/active_record71.rb +6 -6
- data/lib/bullet/active_record72.rb +6 -6
- data/lib/bullet/active_record80.rb +6 -6
- data/lib/bullet/active_record81.rb +321 -0
- data/lib/bullet/dependency.rb +6 -0
- data/lib/bullet/detector/n_plus_one_query.rb +2 -1
- data/lib/bullet/mongoid4x.rb +2 -0
- data/lib/bullet/mongoid5x.rb +2 -0
- data/lib/bullet/mongoid6x.rb +2 -0
- data/lib/bullet/mongoid7x.rb +2 -0
- data/lib/bullet/mongoid8x.rb +2 -0
- data/lib/bullet/mongoid9x.rb +2 -0
- data/lib/bullet/registry/call_stack.rb +7 -2
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f10d7c7ed8bf9cb6e6b3f0d8d2757bfa71a66d8c4f90da0ce708cfce06eddb8
|
|
4
|
+
data.tar.gz: 4986e5b6390339bd6034a598a5f24678dd98c1db907f07de3a2aac1cef4501fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 323f11d8aee1ba3175eb02795aafb800ad1262f4da84333698fa2f73011b5f9912ad7c38fe37f347ac1b492e90cff87db322433fc6ef55262ab1ecf2e6cc02d7
|
|
7
|
+
data.tar.gz: e44277b7412ff505bb3c21047f8eeff5774f367375989192ef27367102eae91f04eb6625f89110160181690ab440051ae6dc2db39d0b5eb89b30fe47a164eab9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/bullet/dependency.rb
CHANGED
|
@@ -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',
|
data/lib/bullet/mongoid4x.rb
CHANGED
|
@@ -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/
|
data/lib/bullet/mongoid5x.rb
CHANGED
|
@@ -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/
|
data/lib/bullet/mongoid6x.rb
CHANGED
|
@@ -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/
|
data/lib/bullet/mongoid7x.rb
CHANGED
|
@@ -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?
|
data/lib/bullet/mongoid8x.rb
CHANGED
|
@@ -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?
|
data/lib/bullet/mongoid9x.rb
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
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
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
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.
|
|
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:
|
|
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: []
|