bullet 8.1.3 → 8.2.0
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 +9 -0
- data/README.md +19 -6
- data/lib/bullet/active_record70.rb +14 -0
- data/lib/bullet/active_record71.rb +14 -0
- data/lib/bullet/active_record72.rb +14 -0
- data/lib/bullet/active_record80.rb +14 -0
- data/lib/bullet/active_record81.rb +14 -0
- data/lib/bullet/dependency.rb +4 -58
- data/lib/bullet/detector/association.rb +2 -2
- data/lib/bullet/detector/n_plus_one_query.rb +1 -1
- data/lib/bullet/rack.rb +35 -2
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +25 -0
- metadata +29 -8
- data/lib/bullet/active_record4.rb +0 -197
- data/lib/bullet/active_record41.rb +0 -191
- data/lib/bullet/active_record42.rb +0 -262
- data/lib/bullet/active_record5.rb +0 -294
- data/lib/bullet/active_record52.rb +0 -278
- data/lib/bullet/active_record60.rb +0 -310
- data/lib/bullet/active_record61.rb +0 -310
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fff8caa10888c5cc1aee062b08ac4c4df6e5911e9b4c592b3e0c89c3faedf089
|
|
4
|
+
data.tar.gz: aebe5eb8c7b8624fc2bbe05f42c4c6e8ba5ef8122f519e4e2f984f83d1ac8762
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6d60f48c25171229b660274b31ae90689f402d0ea1f46fdf2c63467a550765520b46afb70ff683a8beca75da7ddb86629ba5f58e276667a784d37653f3a94688
|
|
7
|
+
data.tar.gz: '06973354f3d5158e1ade0f2898577d772b6669d622b176010b6176dbb8b02e5f67c611fbb55668bfee9c93ea873c1183cd28ba30e778a5a6692d0e050a9a3794'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## Unreleased
|
|
2
2
|
|
|
3
|
+
## 8.2.0 (08/29/2026)
|
|
4
|
+
|
|
5
|
+
* Add thread-safe `Bullet.pause`, `Bullet.resume`, `Bullet.paused?`, and `Bullet.skip` APIs
|
|
6
|
+
* Add configurable footer positioning
|
|
7
|
+
* Fix unused eager loading false positives when N+1 detection is disabled
|
|
8
|
+
* Fix unused eager loading false positives for conditional `has_many :through` associations
|
|
9
|
+
* Fix Ruby 4.0 compatibility issues
|
|
10
|
+
* Drop support for ActiveRecord versions older than 7 and Mongoid versions older than 8
|
|
11
|
+
|
|
3
12
|
## 8.1.3 (06/02/2026)
|
|
4
13
|
|
|
5
14
|
* Handle inversed polymorphic belongs_to false positives
|
data/README.md
CHANGED
|
@@ -69,6 +69,7 @@ config.after_initialize do
|
|
|
69
69
|
Bullet.airbrake = true
|
|
70
70
|
Bullet.rollbar = true
|
|
71
71
|
Bullet.add_footer = true
|
|
72
|
+
Bullet.footer_position = 'bottom_left' # or 'bottom_right', 'top_left', 'top_right'
|
|
72
73
|
Bullet.skip_html_injection = false
|
|
73
74
|
Bullet.skip_http_headers = false
|
|
74
75
|
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
|
|
@@ -97,6 +98,7 @@ The code above will enable all of the Bullet notification systems:
|
|
|
97
98
|
* `Bullet.airbrake`: add notifications to airbrake
|
|
98
99
|
* `Bullet.rollbar`: add notifications to rollbar
|
|
99
100
|
* `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.
|
|
101
|
+
* `Bullet.footer_position`: position of the footer on the page. Valid values are 'bottom_left' (default), 'bottom_right', 'top_left', or 'top_right'. This is useful when the default position clashes with browser UI elements like Chrome's URL preview.
|
|
100
102
|
* `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.
|
|
101
103
|
* `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.
|
|
102
104
|
* `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
|
|
@@ -137,23 +139,34 @@ Bullet.add_safelist :type => :unused_eager_loading, :class_name => "Post", :asso
|
|
|
137
139
|
Bullet.add_safelist :type => :counter_cache, :class_name => "Country", :association => :cities
|
|
138
140
|
```
|
|
139
141
|
|
|
142
|
+
## Skip Bullet in specific actions
|
|
143
|
+
|
|
140
144
|
If you want to skip bullet in some specific controller actions, you can
|
|
141
|
-
|
|
145
|
+
use `Bullet.skip`, which is thread-safe and works correctly in multi-threaded
|
|
146
|
+
environments like Puma.
|
|
142
147
|
|
|
143
148
|
```ruby
|
|
144
149
|
class ApplicationController < ActionController::Base
|
|
145
150
|
around_action :skip_bullet, if: -> { defined?(Bullet) }
|
|
146
151
|
|
|
147
152
|
def skip_bullet
|
|
148
|
-
|
|
149
|
-
Bullet.enable = false
|
|
150
|
-
yield
|
|
151
|
-
ensure
|
|
152
|
-
Bullet.enable = previous_value
|
|
153
|
+
Bullet.skip { yield }
|
|
153
154
|
end
|
|
154
155
|
end
|
|
155
156
|
```
|
|
156
157
|
|
|
158
|
+
For more granular control, you can also use `Bullet.pause` and `Bullet.resume`:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
Bullet.pause # Temporarily disable detection for current thread
|
|
162
|
+
Bullet.resume # Re-enable detection for current thread
|
|
163
|
+
Bullet.paused? # Check if detection is paused for current thread
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Note**: Do not toggle `Bullet.enable` at runtime, as it modifies a global
|
|
167
|
+
flag that is not thread-safe. Use `Bullet.skip`, `Bullet.pause`, or
|
|
168
|
+
`Bullet.start_request`/`Bullet.end_request` instead.
|
|
169
|
+
|
|
157
170
|
## Log
|
|
158
171
|
|
|
159
172
|
The Bullet log `log/bullet.log` will look something like this:
|
|
@@ -109,6 +109,20 @@ module Bullet
|
|
|
109
109
|
super
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
def through_preloaders
|
|
114
|
+
if Bullet.start? && !defined?(@through_preloaders)
|
|
115
|
+
preloaders = super
|
|
116
|
+
preloaders.each do |preloader|
|
|
117
|
+
reflection_name = preloader.send(:reflection).name
|
|
118
|
+
preloader.send(:owners).each do |owner|
|
|
119
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
super
|
|
124
|
+
end
|
|
125
|
+
end
|
|
112
126
|
end
|
|
113
127
|
)
|
|
114
128
|
|
|
@@ -109,6 +109,20 @@ module Bullet
|
|
|
109
109
|
super
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
def through_preloaders
|
|
114
|
+
if Bullet.start? && !defined?(@through_preloaders)
|
|
115
|
+
preloaders = super
|
|
116
|
+
preloaders.each do |preloader|
|
|
117
|
+
reflection_name = preloader.send(:reflection).name
|
|
118
|
+
preloader.send(:owners).each do |owner|
|
|
119
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
super
|
|
124
|
+
end
|
|
125
|
+
end
|
|
112
126
|
end
|
|
113
127
|
)
|
|
114
128
|
|
|
@@ -109,6 +109,20 @@ module Bullet
|
|
|
109
109
|
super
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
def through_preloaders
|
|
114
|
+
if Bullet.start? && !defined?(@through_preloaders)
|
|
115
|
+
preloaders = super
|
|
116
|
+
preloaders.each do |preloader|
|
|
117
|
+
reflection_name = preloader.send(:reflection).name
|
|
118
|
+
preloader.send(:owners).each do |owner|
|
|
119
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
super
|
|
124
|
+
end
|
|
125
|
+
end
|
|
112
126
|
end
|
|
113
127
|
)
|
|
114
128
|
|
|
@@ -109,6 +109,20 @@ module Bullet
|
|
|
109
109
|
super
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
def through_preloaders
|
|
114
|
+
if Bullet.start? && !defined?(@through_preloaders)
|
|
115
|
+
preloaders = super
|
|
116
|
+
preloaders.each do |preloader|
|
|
117
|
+
reflection_name = preloader.send(:reflection).name
|
|
118
|
+
preloader.send(:owners).each do |owner|
|
|
119
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
super
|
|
124
|
+
end
|
|
125
|
+
end
|
|
112
126
|
end
|
|
113
127
|
)
|
|
114
128
|
|
|
@@ -109,6 +109,20 @@ module Bullet
|
|
|
109
109
|
super
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
|
+
|
|
113
|
+
def through_preloaders
|
|
114
|
+
if Bullet.start? && !defined?(@through_preloaders)
|
|
115
|
+
preloaders = super
|
|
116
|
+
preloaders.each do |preloader|
|
|
117
|
+
reflection_name = preloader.send(:reflection).name
|
|
118
|
+
preloader.send(:owners).each do |owner|
|
|
119
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
super
|
|
124
|
+
end
|
|
125
|
+
end
|
|
112
126
|
end
|
|
113
127
|
)
|
|
114
128
|
|
data/lib/bullet/dependency.rb
CHANGED
|
@@ -13,22 +13,8 @@ module Bullet
|
|
|
13
13
|
def active_record_version
|
|
14
14
|
@active_record_version ||=
|
|
15
15
|
begin
|
|
16
|
-
if
|
|
17
|
-
|
|
18
|
-
elsif active_record41?
|
|
19
|
-
'active_record41'
|
|
20
|
-
elsif active_record42?
|
|
21
|
-
'active_record42'
|
|
22
|
-
elsif active_record50?
|
|
23
|
-
'active_record5'
|
|
24
|
-
elsif active_record51?
|
|
25
|
-
'active_record5'
|
|
26
|
-
elsif active_record52?
|
|
27
|
-
'active_record52'
|
|
28
|
-
elsif active_record60?
|
|
29
|
-
'active_record60'
|
|
30
|
-
elsif active_record61?
|
|
31
|
-
'active_record61'
|
|
16
|
+
if active_record6_or_older?
|
|
17
|
+
raise "Bullet no longer supports active_record #{::ActiveRecord::VERSION::STRING}"
|
|
32
18
|
elsif active_record70?
|
|
33
19
|
'active_record70'
|
|
34
20
|
elsif active_record71?
|
|
@@ -66,16 +52,8 @@ module Bullet
|
|
|
66
52
|
end
|
|
67
53
|
end
|
|
68
54
|
|
|
69
|
-
def
|
|
70
|
-
active_record? && ::ActiveRecord::VERSION::MAJOR
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def active_record5?
|
|
74
|
-
active_record? && ::ActiveRecord::VERSION::MAJOR == 5
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def active_record6?
|
|
78
|
-
active_record? && ::ActiveRecord::VERSION::MAJOR == 6
|
|
55
|
+
def active_record6_or_older?
|
|
56
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR < 7
|
|
79
57
|
end
|
|
80
58
|
|
|
81
59
|
def active_record7?
|
|
@@ -86,38 +64,6 @@ module Bullet
|
|
|
86
64
|
active_record? && ::ActiveRecord::VERSION::MAJOR == 8
|
|
87
65
|
end
|
|
88
66
|
|
|
89
|
-
def active_record40?
|
|
90
|
-
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def active_record41?
|
|
94
|
-
active_record4? && ::ActiveRecord::VERSION::MINOR == 1
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def active_record42?
|
|
98
|
-
active_record4? && ::ActiveRecord::VERSION::MINOR == 2
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def active_record50?
|
|
102
|
-
active_record5? && ::ActiveRecord::VERSION::MINOR == 0
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def active_record51?
|
|
106
|
-
active_record5? && ::ActiveRecord::VERSION::MINOR == 1
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def active_record52?
|
|
110
|
-
active_record5? && ::ActiveRecord::VERSION::MINOR == 2
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def active_record60?
|
|
114
|
-
active_record6? && ::ActiveRecord::VERSION::MINOR == 0
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def active_record61?
|
|
118
|
-
active_record6? && ::ActiveRecord::VERSION::MINOR == 1
|
|
119
|
-
end
|
|
120
|
-
|
|
121
67
|
def active_record70?
|
|
122
68
|
active_record7? && ::ActiveRecord::VERSION::MINOR == 0
|
|
123
69
|
end
|
|
@@ -8,7 +8,7 @@ module Bullet
|
|
|
8
8
|
class << self
|
|
9
9
|
def add_object_associations(object, associations)
|
|
10
10
|
return unless Bullet.start?
|
|
11
|
-
return
|
|
11
|
+
return unless Bullet.n_plus_one_query_enable? || Bullet.unused_eager_loading_enable?
|
|
12
12
|
return unless object.bullet_primary_key_value
|
|
13
13
|
|
|
14
14
|
Bullet.debug(
|
|
@@ -21,7 +21,7 @@ module Bullet
|
|
|
21
21
|
|
|
22
22
|
def add_call_object_associations(object, associations)
|
|
23
23
|
return unless Bullet.start?
|
|
24
|
-
return
|
|
24
|
+
return unless Bullet.n_plus_one_query_enable? || Bullet.unused_eager_loading_enable?
|
|
25
25
|
return unless object.bullet_primary_key_value
|
|
26
26
|
|
|
27
27
|
Bullet.debug(
|
|
@@ -15,7 +15,7 @@ module Bullet
|
|
|
15
15
|
# if it is, keeps this unpreload associations and caller.
|
|
16
16
|
def call_association(object, associations, caller_stack = nil, inversed: false)
|
|
17
17
|
return unless Bullet.start?
|
|
18
|
-
return unless Bullet.n_plus_one_query_enable?
|
|
18
|
+
return unless Bullet.n_plus_one_query_enable? || Bullet.unused_eager_loading_enable?
|
|
19
19
|
return unless object.bullet_primary_key_value
|
|
20
20
|
|
|
21
21
|
# Record before early-returns so legitimate reads that bypass SQL
|
data/lib/bullet/rack.rb
CHANGED
|
@@ -76,8 +76,9 @@ module Bullet
|
|
|
76
76
|
|
|
77
77
|
# Make footer styles work with ContentSecurityPolicy style-src as self
|
|
78
78
|
def footer_style(nonce = nil)
|
|
79
|
+
position_css = footer_position_css
|
|
79
80
|
css = <<~CSS
|
|
80
|
-
details#bullet-footer {cursor: pointer; position: fixed;
|
|
81
|
+
details#bullet-footer {cursor: pointer; position: fixed; #{position_css}; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; #{footer_border_radius}; border: 1px solid #9b1c1c;}
|
|
81
82
|
details#bullet-footer summary {font-weight: 600; padding: 2px 8px;}
|
|
82
83
|
details#bullet-footer div {padding: 8px; border-top: 1px solid #9b1c1c;}
|
|
83
84
|
CSS
|
|
@@ -88,6 +89,38 @@ module Bullet
|
|
|
88
89
|
end
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def footer_position_css
|
|
95
|
+
case Bullet.footer_position
|
|
96
|
+
when 'bottom_left'
|
|
97
|
+
'left: 0px; bottom: 0px'
|
|
98
|
+
when 'bottom_right'
|
|
99
|
+
'right: 0px; bottom: 0px'
|
|
100
|
+
when 'top_left'
|
|
101
|
+
'left: 0px; top: 0px'
|
|
102
|
+
when 'top_right'
|
|
103
|
+
'right: 0px; top: 0px'
|
|
104
|
+
else
|
|
105
|
+
'left: 0px; bottom: 0px'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def footer_border_radius
|
|
110
|
+
case Bullet.footer_position
|
|
111
|
+
when 'bottom_left'
|
|
112
|
+
'border-radius: 0px 8px 0px 0px'
|
|
113
|
+
when 'bottom_right'
|
|
114
|
+
'border-radius: 8px 0px 0px 0px'
|
|
115
|
+
when 'top_left'
|
|
116
|
+
'border-radius: 0px 0px 8px 0px'
|
|
117
|
+
when 'top_right'
|
|
118
|
+
'border-radius: 0px 0px 0px 8px'
|
|
119
|
+
else
|
|
120
|
+
'border-radius: 0px 8px 0px 0px'
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
91
124
|
def set_header(headers, header_name, header_array)
|
|
92
125
|
# Many proxy applications such as Nginx and AWS ELB limit
|
|
93
126
|
# the size a header to 8KB, so truncate the list of reports to
|
|
@@ -123,7 +156,7 @@ module Bullet
|
|
|
123
156
|
end
|
|
124
157
|
|
|
125
158
|
def html_request?(headers, response)
|
|
126
|
-
headers['Content-Type']&.include?('text/html')
|
|
159
|
+
headers['Content-Type']&.include?('text/html') || false
|
|
127
160
|
end
|
|
128
161
|
|
|
129
162
|
def response_body(response)
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -42,6 +42,7 @@ module Bullet
|
|
|
42
42
|
:skip_html_injection
|
|
43
43
|
attr_reader :safelist
|
|
44
44
|
attr_accessor :add_footer,
|
|
45
|
+
:footer_position,
|
|
45
46
|
:orm_patches_applied,
|
|
46
47
|
:skip_http_headers,
|
|
47
48
|
:always_append_html_body,
|
|
@@ -188,6 +189,26 @@ module Bullet
|
|
|
188
189
|
enable? && Thread.current.thread_variable_get(:bullet_start)
|
|
189
190
|
end
|
|
190
191
|
|
|
192
|
+
def pause
|
|
193
|
+
Thread.current.thread_variable_set(:bullet_start, false)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def resume
|
|
197
|
+
Thread.current.thread_variable_set(:bullet_start, true)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def paused?
|
|
201
|
+
enable? && !Thread.current.thread_variable_get(:bullet_start)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def skip
|
|
205
|
+
bullet_was_started = start?
|
|
206
|
+
pause if bullet_was_started
|
|
207
|
+
yield
|
|
208
|
+
ensure
|
|
209
|
+
resume if bullet_was_started
|
|
210
|
+
end
|
|
211
|
+
|
|
191
212
|
def notification_collector
|
|
192
213
|
Thread.current.thread_variable_get(:bullet_notification_collector)
|
|
193
214
|
end
|
|
@@ -259,6 +280,10 @@ module Bullet
|
|
|
259
280
|
UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
|
|
260
281
|
end
|
|
261
282
|
|
|
283
|
+
def footer_position
|
|
284
|
+
@footer_position || 'bottom_left'
|
|
285
|
+
end
|
|
286
|
+
|
|
262
287
|
def inject_into_page?
|
|
263
288
|
return false if defined?(@skip_html_injection) && @skip_html_injection
|
|
264
289
|
|
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.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Huang
|
|
@@ -37,6 +37,34 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.11'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ostruct
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: logger
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
40
68
|
description: help to kill N+1 queries and unused eager loading.
|
|
41
69
|
email:
|
|
42
70
|
- flyerhzm@gmail.com
|
|
@@ -49,13 +77,6 @@ files:
|
|
|
49
77
|
- README.md
|
|
50
78
|
- lib/bullet.rb
|
|
51
79
|
- lib/bullet/active_job.rb
|
|
52
|
-
- lib/bullet/active_record4.rb
|
|
53
|
-
- lib/bullet/active_record41.rb
|
|
54
|
-
- lib/bullet/active_record42.rb
|
|
55
|
-
- lib/bullet/active_record5.rb
|
|
56
|
-
- lib/bullet/active_record52.rb
|
|
57
|
-
- lib/bullet/active_record60.rb
|
|
58
|
-
- lib/bullet/active_record61.rb
|
|
59
80
|
- lib/bullet/active_record70.rb
|
|
60
81
|
- lib/bullet/active_record71.rb
|
|
61
82
|
- lib/bullet/active_record72.rb
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bullet
|
|
4
|
-
module ActiveRecord
|
|
5
|
-
def self.enable
|
|
6
|
-
require 'active_record'
|
|
7
|
-
::ActiveRecord::Base.class_eval do
|
|
8
|
-
class << self
|
|
9
|
-
alias_method :origin_find_by_sql, :find_by_sql
|
|
10
|
-
def find_by_sql(sql, binds = [])
|
|
11
|
-
result = origin_find_by_sql(sql, binds)
|
|
12
|
-
if Bullet.start?
|
|
13
|
-
if result.is_a? Array
|
|
14
|
-
if result.size > 1
|
|
15
|
-
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
16
|
-
Bullet::Detector::CounterCache.add_possible_objects(result)
|
|
17
|
-
elsif result.size == 1
|
|
18
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
|
|
19
|
-
Bullet::Detector::CounterCache.add_impossible_object(result.first)
|
|
20
|
-
end
|
|
21
|
-
elsif result.is_a? ::ActiveRecord::Base
|
|
22
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
|
|
23
|
-
Bullet::Detector::CounterCache.add_impossible_object(result)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
result
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
::ActiveRecord::Relation.class_eval do
|
|
32
|
-
alias_method :origin_to_a, :to_a
|
|
33
|
-
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
34
|
-
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
35
|
-
def to_a
|
|
36
|
-
records = origin_to_a
|
|
37
|
-
if Bullet.start?
|
|
38
|
-
if records.size > 1
|
|
39
|
-
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
40
|
-
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
41
|
-
elsif records.size == 1
|
|
42
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
43
|
-
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
records
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
::ActiveRecord::Persistence.class_eval do
|
|
51
|
-
def _create_record_with_bullet(*args)
|
|
52
|
-
_create_record_without_bullet(*args).tap do
|
|
53
|
-
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
|
|
54
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
alias_method_chain :_create_record, :bullet
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
::ActiveRecord::Associations::Preloader.class_eval do
|
|
61
|
-
# include query for one to many associations.
|
|
62
|
-
# keep this eager loadings.
|
|
63
|
-
alias_method :origin_initialize, :initialize
|
|
64
|
-
def initialize(records, associations, preload_scope = nil)
|
|
65
|
-
origin_initialize(records, associations, preload_scope)
|
|
66
|
-
|
|
67
|
-
if Bullet.start?
|
|
68
|
-
records = [records].flatten.compact.uniq
|
|
69
|
-
return if records.empty?
|
|
70
|
-
|
|
71
|
-
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
|
|
72
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
::ActiveRecord::FinderMethods.class_eval do
|
|
78
|
-
# add includes in scope
|
|
79
|
-
alias_method :origin_find_with_associations, :find_with_associations
|
|
80
|
-
def find_with_associations
|
|
81
|
-
records = origin_find_with_associations
|
|
82
|
-
if Bullet.start?
|
|
83
|
-
associations = (eager_load_values + includes_values).uniq
|
|
84
|
-
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
|
|
85
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
86
|
-
end
|
|
87
|
-
records
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
::ActiveRecord::Associations::JoinDependency.class_eval do
|
|
92
|
-
alias_method :origin_instantiate, :instantiate
|
|
93
|
-
alias_method :origin_construct_association, :construct_association
|
|
94
|
-
|
|
95
|
-
def instantiate(rows)
|
|
96
|
-
@bullet_eager_loadings = {}
|
|
97
|
-
records = origin_instantiate(rows)
|
|
98
|
-
|
|
99
|
-
if Bullet.start?
|
|
100
|
-
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
|
|
101
|
-
objects = eager_loadings_hash.keys
|
|
102
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
records
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# call join associations
|
|
109
|
-
def construct_association(record, join, row)
|
|
110
|
-
result = origin_construct_association(record, join, row)
|
|
111
|
-
|
|
112
|
-
if Bullet.start?
|
|
113
|
-
associations = [join.reflection.name]
|
|
114
|
-
if join.reflection.nested?
|
|
115
|
-
associations << join.reflection.through_reflection.name
|
|
116
|
-
end
|
|
117
|
-
associations.each do |association|
|
|
118
|
-
Bullet::Detector::Association.add_object_associations(record, association)
|
|
119
|
-
Bullet::Detector::NPlusOneQuery.call_association(record, association)
|
|
120
|
-
@bullet_eager_loadings[record.class] ||= {}
|
|
121
|
-
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
122
|
-
@bullet_eager_loadings[record.class][record] << association
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
result
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
::ActiveRecord::Associations::CollectionAssociation.class_eval do
|
|
131
|
-
# call one to many associations
|
|
132
|
-
alias_method :origin_load_target, :load_target
|
|
133
|
-
def load_target
|
|
134
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
|
|
135
|
-
origin_load_target
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
alias_method :origin_include?, :include?
|
|
139
|
-
def include?(object)
|
|
140
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start?
|
|
141
|
-
origin_include?(object)
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
|
146
|
-
alias_method :origin_empty?, :empty?
|
|
147
|
-
def empty?
|
|
148
|
-
if Bullet.start? && !loaded? && !has_cached_counter?(@reflection)
|
|
149
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
150
|
-
end
|
|
151
|
-
origin_empty?
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
::ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
|
|
156
|
-
alias_method :origin_empty?, :empty?
|
|
157
|
-
def empty?
|
|
158
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) if Bullet.start? && !loaded?
|
|
159
|
-
origin_empty?
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
::ActiveRecord::Associations::SingularAssociation.class_eval do
|
|
164
|
-
# call has_one and belongs_to associations
|
|
165
|
-
alias_method :origin_reader, :reader
|
|
166
|
-
def reader(force_reload = false)
|
|
167
|
-
result = origin_reader(force_reload)
|
|
168
|
-
if Bullet.start?
|
|
169
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name, inversed: @inversed)
|
|
170
|
-
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) unless @inversed
|
|
171
|
-
end
|
|
172
|
-
result
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
|
177
|
-
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
178
|
-
|
|
179
|
-
def has_cached_counter?(reflection = reflection())
|
|
180
|
-
result = origin_has_cached_counter?(reflection)
|
|
181
|
-
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) if Bullet.start? && !result
|
|
182
|
-
result
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
::ActiveRecord::Associations::CollectionProxy.class_eval do
|
|
187
|
-
def count(column_name = nil, options = {})
|
|
188
|
-
if Bullet.start?
|
|
189
|
-
Bullet::Detector::CounterCache.add_counter_cache(proxy_association.owner, proxy_association.reflection.name)
|
|
190
|
-
Bullet::Detector::NPlusOneQuery.call_association(proxy_association.owner, proxy_association.reflection.name)
|
|
191
|
-
end
|
|
192
|
-
super(column_name, options)
|
|
193
|
-
end
|
|
194
|
-
end
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
end
|