bullet 8.0.3 → 8.1.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 +30 -0
- data/README.md +13 -2
- data/lib/bullet/active_record4.rb +4 -1
- data/lib/bullet/active_record41.rb +4 -1
- data/lib/bullet/active_record42.rb +4 -1
- data/lib/bullet/active_record5.rb +1 -0
- data/lib/bullet/active_record52.rb +1 -0
- data/lib/bullet/active_record60.rb +1 -0
- data/lib/bullet/active_record61.rb +1 -0
- data/lib/bullet/active_record70.rb +1 -0
- data/lib/bullet/active_record71.rb +1 -0
- data/lib/bullet/active_record72.rb +1 -0
- data/lib/bullet/active_record80.rb +1 -0
- data/lib/bullet/active_record81.rb +319 -0
- data/lib/bullet/dependency.rb +6 -0
- data/lib/bullet/ext/object.rb +3 -3
- 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/rack.rb +55 -28
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +2 -2
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca6723ad41e0d2c1303ee57eda90abd7973379fff9760a4d94ccc54765af56d5
|
|
4
|
+
data.tar.gz: a96d3fab59f09fa013794cdb4fdf1ecc1e033a94650005e2a42e089ba590742d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 60ad7aab73a64546410654d8bf1bc6b07671efd9b6d3860c0192a8287ef7446f287fea34f0011d1d30f3f5a74e6fb5391a8826bca9f86b45813772395795415b
|
|
7
|
+
data.tar.gz: f9076576155195baee90fbee13217bf5e4b6a83faff5e11bfadfe91ec8a9f9db3e06df1014c78549893abb767139fdde904a1c097b1867976784f9cba77cf9c0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
## Next Release
|
|
2
2
|
|
|
3
|
+
## 8.1.0 (10/23/2025)
|
|
4
|
+
|
|
5
|
+
* Make `get_relation` private
|
|
6
|
+
* Support Rails 8.1
|
|
7
|
+
|
|
8
|
+
## 8.0.8 (05/30/2025)
|
|
9
|
+
|
|
10
|
+
* Add middleware after initializers
|
|
11
|
+
* Fix bullet composite primary key retrieval
|
|
12
|
+
|
|
13
|
+
## 8.0.7 (05/15/2025)
|
|
14
|
+
|
|
15
|
+
* Try to insert `Bullet::Rack` properly
|
|
16
|
+
|
|
17
|
+
## 8.0.6 (05/07/2025)
|
|
18
|
+
|
|
19
|
+
* Add CSP nonce for footer styles as well
|
|
20
|
+
* Add support for OpenTelemetry reporting
|
|
21
|
+
|
|
22
|
+
## 8.0.5 (04/21/2025)
|
|
23
|
+
|
|
24
|
+
* Properly insert ContentSecurityPolicy middleware
|
|
25
|
+
* Properly parse query string
|
|
26
|
+
|
|
27
|
+
## 8.0.4 (04/18/2025)
|
|
28
|
+
|
|
29
|
+
* Insert bullet middleware before `ContentSecurityPolicy`
|
|
30
|
+
* Support url query `skip_html_injection=true`
|
|
31
|
+
* Mark object as impossible after updating inversed
|
|
32
|
+
|
|
3
33
|
## 8.0.3 (04/04/2025)
|
|
4
34
|
|
|
5
35
|
* Update non persisted `inversed_objects`
|
data/README.md
CHANGED
|
@@ -71,9 +71,14 @@ config.after_initialize do
|
|
|
71
71
|
Bullet.rollbar = true
|
|
72
72
|
Bullet.add_footer = true
|
|
73
73
|
Bullet.skip_html_injection = false
|
|
74
|
+
Bullet.skip_http_headers = false
|
|
74
75
|
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
|
|
75
76
|
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
|
|
76
77
|
Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
|
|
78
|
+
Bullet.opentelemetry = true
|
|
79
|
+
Bullet.raise = false
|
|
80
|
+
Bullet.always_append_html_body = false
|
|
81
|
+
Bullet.skip_user_in_notification = false
|
|
77
82
|
end
|
|
78
83
|
```
|
|
79
84
|
|
|
@@ -81,6 +86,7 @@ The notifier of Bullet is a wrap of [uniform_notifier](https://github.com/flyerh
|
|
|
81
86
|
|
|
82
87
|
The code above will enable all of the Bullet notification systems:
|
|
83
88
|
* `Bullet.enable`: enable Bullet gem, otherwise do nothing
|
|
89
|
+
* `Bullet.sentry`: add notifications to sentry
|
|
84
90
|
* `Bullet.alert`: pop up a JavaScript alert in the browser
|
|
85
91
|
* `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)
|
|
86
92
|
* `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
|
|
@@ -88,10 +94,9 @@ The code above will enable all of the Bullet notification systems:
|
|
|
88
94
|
* `Bullet.rails_logger`: add warnings directly to the Rails log
|
|
89
95
|
* `Bullet.honeybadger`: add notifications to Honeybadger
|
|
90
96
|
* `Bullet.bugsnag`: add notifications to bugsnag
|
|
91
|
-
* `Bullet.airbrake`: add notifications to airbrake
|
|
92
97
|
* `Bullet.appsignal`: add notifications to AppSignal
|
|
98
|
+
* `Bullet.airbrake`: add notifications to airbrake
|
|
93
99
|
* `Bullet.rollbar`: add notifications to rollbar
|
|
94
|
-
* `Bullet.sentry`: add notifications to sentry
|
|
95
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.
|
|
96
101
|
* `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.
|
|
97
102
|
* `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.
|
|
@@ -100,6 +105,7 @@ The code above will enable all of the Bullet notification systems:
|
|
|
100
105
|
Each item can be a string (match substring), a regex, or an array where the first item is a path to match, and the second
|
|
101
106
|
item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file.
|
|
102
107
|
* `Bullet.slack`: add notifications to slack
|
|
108
|
+
* `Bullet.opentelemetry`: add notifications to OpenTelemetry
|
|
103
109
|
* `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries
|
|
104
110
|
* `Bullet.always_append_html_body`: always append the html body even if no notifications are present. Note: `console` or `add_footer` must also be true. Useful for Single Page Applications where the initial page load might not have any notifications present.
|
|
105
111
|
* `Bullet.skip_user_in_notification`: exclude the OS user (`whoami`) from notifications.
|
|
@@ -192,6 +198,11 @@ see [https://github.com/flyerhzm/uniform_notifier](https://github.com/flyerhzm/u
|
|
|
192
198
|
|
|
193
199
|
Growl support is dropped from uniform_notifier 1.16.0, if you still want it, please use uniform_notifier 1.15.0.
|
|
194
200
|
|
|
201
|
+
## URL query control
|
|
202
|
+
|
|
203
|
+
You can add the URL query parameter `skip_html_injection` to make the current HTML request behave as if `Bullet.skip_html_injection` is enabled,
|
|
204
|
+
e.g. `http://localhost:3000/posts?skip_html_injection=true`
|
|
205
|
+
|
|
195
206
|
## Important
|
|
196
207
|
|
|
197
208
|
If you find Bullet does not work for you, *please disable your browser's cache*.
|
|
@@ -49,7 +49,10 @@ module Bullet
|
|
|
49
49
|
|
|
50
50
|
::ActiveRecord::Persistence.class_eval do
|
|
51
51
|
def _create_record_with_bullet(*args)
|
|
52
|
-
_create_record_without_bullet(*args).tap
|
|
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
|
|
53
56
|
end
|
|
54
57
|
alias_method_chain :_create_record, :bullet
|
|
55
58
|
end
|
|
@@ -52,7 +52,10 @@ module Bullet
|
|
|
52
52
|
|
|
53
53
|
::ActiveRecord::Persistence.class_eval do
|
|
54
54
|
def _create_record_with_bullet(*args)
|
|
55
|
-
_create_record_without_bullet(*args).tap
|
|
55
|
+
_create_record_without_bullet(*args).tap do
|
|
56
|
+
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
|
|
57
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
|
58
|
+
end
|
|
56
59
|
end
|
|
57
60
|
alias_method_chain :_create_record, :bullet
|
|
58
61
|
end
|
|
@@ -45,7 +45,10 @@ module Bullet
|
|
|
45
45
|
|
|
46
46
|
::ActiveRecord::Persistence.class_eval do
|
|
47
47
|
def _create_record_with_bullet(*args)
|
|
48
|
-
_create_record_without_bullet(*args).tap
|
|
48
|
+
_create_record_without_bullet(*args).tap do
|
|
49
|
+
Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
|
|
50
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
|
51
|
+
end
|
|
49
52
|
end
|
|
50
53
|
alias_method_chain :_create_record, :bullet
|
|
51
54
|
end
|
|
@@ -0,0 +1,319 @@
|
|
|
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(sql, binds = [], preparable: nil, allow_retry: false, &block)
|
|
20
|
+
result = super
|
|
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(result_set, strict_loading_value, &block)
|
|
118
|
+
@bullet_eager_loadings = {}
|
|
119
|
+
records = super
|
|
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(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
|
134
|
+
if Bullet.start?
|
|
135
|
+
unless ar_parent.nil?
|
|
136
|
+
parent.children.each do |node|
|
|
137
|
+
key = aliases.column_alias(node, node.primary_key)
|
|
138
|
+
id = row[key]
|
|
139
|
+
next unless id.nil?
|
|
140
|
+
|
|
141
|
+
associations = [node.reflection.name]
|
|
142
|
+
if node.reflection.through_reflection?
|
|
143
|
+
associations << node.reflection.through_reflection.name
|
|
144
|
+
end
|
|
145
|
+
associations.each do |association|
|
|
146
|
+
Bullet::Detector::Association.add_object_associations(ar_parent, association)
|
|
147
|
+
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
|
|
148
|
+
@bullet_eager_loadings[ar_parent.class] ||= {}
|
|
149
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
|
|
150
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] << association
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
super
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# call join associations
|
|
160
|
+
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
|
161
|
+
result = super
|
|
162
|
+
|
|
163
|
+
if Bullet.start?
|
|
164
|
+
associations = [node.reflection.name]
|
|
165
|
+
if node.reflection.through_reflection?
|
|
166
|
+
associations << node.reflection.through_reflection.name
|
|
167
|
+
end
|
|
168
|
+
associations.each do |association|
|
|
169
|
+
Bullet::Detector::Association.add_object_associations(record, association)
|
|
170
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, association)
|
|
171
|
+
@bullet_eager_loadings[record.class] ||= {}
|
|
172
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
173
|
+
@bullet_eager_loadings[record.class][record] << association
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
result
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
::ActiveRecord::Associations::Association.prepend(
|
|
183
|
+
Module.new do
|
|
184
|
+
def inversed_from(record)
|
|
185
|
+
if Bullet.start?
|
|
186
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
|
187
|
+
end
|
|
188
|
+
super
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def inversed_from_queries(record)
|
|
192
|
+
if Bullet.start? && inversable?(record)
|
|
193
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
|
194
|
+
end
|
|
195
|
+
super
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
|
201
|
+
Module.new do
|
|
202
|
+
def load_target
|
|
203
|
+
records = super
|
|
204
|
+
|
|
205
|
+
if Bullet.start?
|
|
206
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
207
|
+
association = owner.association(reflection.through_reflection.name)
|
|
208
|
+
if association.loaded?
|
|
209
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
210
|
+
Array.wrap(association.target).each do |through_record|
|
|
211
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
if reflection.through_reflection != through_reflection
|
|
215
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
220
|
+
if records.first.class.name !~ /^HABTM_/
|
|
221
|
+
if records.size > 1
|
|
222
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
223
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
224
|
+
elsif records.size == 1
|
|
225
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
226
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
records
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def empty?
|
|
234
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
|
235
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
236
|
+
end
|
|
237
|
+
super
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def include?(object)
|
|
241
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
|
|
242
|
+
super
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
::ActiveRecord::Associations::SingularAssociation.prepend(
|
|
248
|
+
Module.new do
|
|
249
|
+
# call has_one and belongs_to associations
|
|
250
|
+
def reader
|
|
251
|
+
result = super
|
|
252
|
+
|
|
253
|
+
if Bullet.start?
|
|
254
|
+
if owner.class.name !~ /^HABTM_/
|
|
255
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
256
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
257
|
+
association = owner.association(reflection.through_reflection.name)
|
|
258
|
+
Array.wrap(association.target).each do |through_record|
|
|
259
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
if reflection.through_reflection != through_reflection
|
|
263
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
267
|
+
|
|
268
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
|
269
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
270
|
+
else
|
|
271
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
result
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(
|
|
281
|
+
Module.new do
|
|
282
|
+
def empty?
|
|
283
|
+
result = super
|
|
284
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
|
285
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
286
|
+
end
|
|
287
|
+
result
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def count_records
|
|
291
|
+
result = reflection.has_cached_counter?
|
|
292
|
+
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
|
|
293
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
|
294
|
+
end
|
|
295
|
+
super
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
::ActiveRecord::Associations::CollectionProxy.prepend(
|
|
301
|
+
Module.new do
|
|
302
|
+
def count(column_name = nil)
|
|
303
|
+
if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
|
|
304
|
+
Bullet::Detector::CounterCache.add_counter_cache(
|
|
305
|
+
proxy_association.owner,
|
|
306
|
+
proxy_association.reflection.name
|
|
307
|
+
)
|
|
308
|
+
Bullet::Detector::NPlusOneQuery.call_association(
|
|
309
|
+
proxy_association.owner,
|
|
310
|
+
proxy_association.reflection.name
|
|
311
|
+
)
|
|
312
|
+
end
|
|
313
|
+
super(column_name)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
)
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
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
|
data/lib/bullet/ext/object.rb
CHANGED
|
@@ -26,10 +26,10 @@ module Bullet
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
28
|
def bullet_join_potential_composite_primary_key(primary_keys)
|
|
29
|
-
return
|
|
29
|
+
return read_attribute(primary_keys) unless primary_keys.is_a?(Enumerable)
|
|
30
30
|
|
|
31
|
-
primary_keys.map { |primary_key|
|
|
32
|
-
.join(',')
|
|
31
|
+
primary_keys.map { |primary_key| read_attribute primary_key }
|
|
32
|
+
.compact.join(',')
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
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?
|
data/lib/bullet/rack.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'rack/request'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'cgi'
|
|
6
|
+
|
|
3
7
|
module Bullet
|
|
4
8
|
class Rack
|
|
5
9
|
include Dependency
|
|
6
10
|
|
|
7
|
-
NONCE_MATCHER = /script-src .*'nonce-(?<nonce>[A-Za-z0-9+\/]+={0,2})'/
|
|
11
|
+
NONCE_MATCHER = /(script|style)-src .*'nonce-(?<nonce>[A-Za-z0-9+\/]+={0,2})'/
|
|
8
12
|
|
|
9
13
|
def initialize(app)
|
|
10
14
|
@app = app
|
|
@@ -19,12 +23,13 @@ module Bullet
|
|
|
19
23
|
response_body = nil
|
|
20
24
|
|
|
21
25
|
if Bullet.notification? || Bullet.always_append_html_body
|
|
22
|
-
|
|
26
|
+
request = ::Rack::Request.new(env)
|
|
27
|
+
if Bullet.inject_into_page? && !skip_html_injection?(request) && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
|
23
28
|
if html_request?(headers, response)
|
|
24
29
|
response_body = response_body(response)
|
|
25
30
|
|
|
26
31
|
with_security_policy_nonce(headers) do |nonce|
|
|
27
|
-
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
|
32
|
+
response_body = append_to_html_body(response_body, footer_note(nonce)) if Bullet.add_footer
|
|
28
33
|
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
29
34
|
if Bullet.add_footer && !Bullet.skip_http_headers
|
|
30
35
|
response_body = append_to_html_body(response_body, xhr_script(nonce))
|
|
@@ -65,16 +70,48 @@ module Bullet
|
|
|
65
70
|
end
|
|
66
71
|
end
|
|
67
72
|
|
|
68
|
-
def footer_note
|
|
69
|
-
|
|
73
|
+
def footer_note(nonce = nil)
|
|
74
|
+
%(<details id="bullet-footer" data-is-bullet-footer><summary>Bullet Warnings</summary><div>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message(nonce)}</div>#{footer_style(nonce)}</details>)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Make footer styles work with ContentSecurityPolicy style-src as self
|
|
78
|
+
def footer_style(nonce = nil)
|
|
79
|
+
css = <<~CSS
|
|
80
|
+
details#bullet-footer {cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;}
|
|
81
|
+
details#bullet-footer summary {font-weight: 600; padding: 2px 8px;}
|
|
82
|
+
details#bullet-footer div {padding: 8px; border-top: 1px solid #9b1c1c;}
|
|
83
|
+
CSS
|
|
84
|
+
if nonce
|
|
85
|
+
%(<style type="text/css" nonce="#{nonce}">#{css}</style>)
|
|
86
|
+
else
|
|
87
|
+
%(<style type="text/css">#{css}</style>)
|
|
88
|
+
end
|
|
70
89
|
end
|
|
71
90
|
|
|
72
91
|
def set_header(headers, header_name, header_array)
|
|
73
92
|
# Many proxy applications such as Nginx and AWS ELB limit
|
|
74
93
|
# the size a header to 8KB, so truncate the list of reports to
|
|
75
94
|
# be under that limit
|
|
76
|
-
header_array.pop while header_array.
|
|
77
|
-
headers[header_name] = header_array
|
|
95
|
+
header_array.pop while JSON.generate(header_array).length > 8 * 1024
|
|
96
|
+
headers[header_name] = JSON.generate(header_array)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def skip_html_injection?(request)
|
|
100
|
+
query_string = request.env['QUERY_STRING']
|
|
101
|
+
return false if query_string.nil? || query_string.empty?
|
|
102
|
+
|
|
103
|
+
params = simple_parse_query_string(query_string)
|
|
104
|
+
params['skip_html_injection'] == 'true'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Simple query string parser
|
|
108
|
+
def simple_parse_query_string(query_string)
|
|
109
|
+
params = {}
|
|
110
|
+
query_string.split('&').each do |pair|
|
|
111
|
+
key, value = pair.split('=', 2).map { |s| CGI.unescape(s) }
|
|
112
|
+
params[key] = value if key && !key.empty?
|
|
113
|
+
end
|
|
114
|
+
params
|
|
78
115
|
end
|
|
79
116
|
|
|
80
117
|
def file?(headers)
|
|
@@ -99,28 +136,18 @@ module Bullet
|
|
|
99
136
|
|
|
100
137
|
private
|
|
101
138
|
|
|
102
|
-
def
|
|
103
|
-
<<~EOF
|
|
104
|
-
id="bullet-footer" data-is-bullet-footer
|
|
105
|
-
style="cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;"
|
|
106
|
-
EOF
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def summary_attributes
|
|
110
|
-
<<~EOF
|
|
111
|
-
style="font-weight: 600; padding: 2px 8px"
|
|
112
|
-
EOF
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def footer_content_attributes
|
|
116
|
-
<<~EOF
|
|
117
|
-
style="padding: 8px; border-top: 1px solid #9b1c1c;"
|
|
118
|
-
EOF
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def footer_console_message
|
|
139
|
+
def footer_console_message(nonce = nil)
|
|
122
140
|
if Bullet.console_enabled?
|
|
123
|
-
|
|
141
|
+
footer = %(<br/><span id="console-message">See 'Uniform Notifier' in JS Console for Stacktrace</span>)
|
|
142
|
+
css = "details#bullet-footer #console-message {font-style: italic;}"
|
|
143
|
+
style =
|
|
144
|
+
if nonce
|
|
145
|
+
%(<style type="text/css" nonce="#{nonce}">#{css}</style>)
|
|
146
|
+
else
|
|
147
|
+
%(<style type="text/css">#{css}</style>)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
footer + style
|
|
124
151
|
end
|
|
125
152
|
end
|
|
126
153
|
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -23,8 +23,8 @@ module Bullet
|
|
|
23
23
|
|
|
24
24
|
if defined?(Rails::Railtie)
|
|
25
25
|
class BulletRailtie < Rails::Railtie
|
|
26
|
-
initializer 'bullet.
|
|
27
|
-
if defined?(ActionDispatch::ContentSecurityPolicy::Middleware) && Rails.application.config.content_security_policy
|
|
26
|
+
initializer 'bullet.add_middleware', after: :load_config_initializers do |app|
|
|
27
|
+
if defined?(ActionDispatch::ContentSecurityPolicy::Middleware) && Rails.application.config.content_security_policy && !app.config.api_only
|
|
28
28
|
app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, Bullet::Rack
|
|
29
29
|
else
|
|
30
30
|
app.middleware.use Bullet::Rack
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bullet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.0
|
|
4
|
+
version: 8.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Richard Huang
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -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.
|
|
116
|
+
rubygems_version: 3.6.9
|
|
116
117
|
specification_version: 4
|
|
117
118
|
summary: help to kill N+1 queries and unused eager loading.
|
|
118
119
|
test_files: []
|