bullet 7.0.1 → 7.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +2 -2
- data/CHANGELOG.md +30 -0
- data/MIT-LICENSE +1 -1
- data/README.md +22 -17
- data/lib/bullet/active_record5.rb +10 -8
- data/lib/bullet/active_record52.rb +10 -8
- data/lib/bullet/active_record60.rb +9 -7
- data/lib/bullet/active_record61.rb +9 -7
- data/lib/bullet/active_record70.rb +16 -7
- data/lib/bullet/bullet_xhr.js +3 -3
- data/lib/bullet/detector/association.rb +8 -0
- data/lib/bullet/detector/counter_cache.rb +2 -2
- data/lib/bullet/detector/n_plus_one_query.rb +23 -12
- data/lib/bullet/detector/unused_eager_loading.rb +3 -3
- data/lib/bullet/mongoid7x.rb +8 -10
- data/lib/bullet/rack.rb +39 -7
- data/lib/bullet/registry/call_stack.rb +12 -0
- data/lib/bullet/registry.rb +1 -0
- data/lib/bullet/stack_trace_filter.rb +10 -8
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +7 -2
- data/lib/generators/bullet/install_generator.rb +0 -1
- data/spec/bullet/detector/n_plus_one_query_spec.rb +1 -33
- data/spec/bullet/detector/unused_eager_loading_spec.rb +5 -0
- data/spec/bullet/notification/base_spec.rb +4 -4
- data/spec/bullet/rack_spec.rb +18 -7
- data/spec/bullet/stack_trace_filter_spec.rb +26 -0
- data/spec/integration/active_record/association_spec.rb +31 -2
- data/spec/support/sqlite_seed.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0f47e147f5df074217ee7a65123f54a1918f98bde162c7d4bf1f76bffc9f15a6
|
4
|
+
data.tar.gz: e05e48b96a5e68bfbcac63f4dce4601d717ec1a32ee83a5c45aad738e1bfc48b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87bf095754befedb8f1137ae039191807baa1f1bcadb09c923b4f1a6abd385e73e4143e50077358a8f7155e3bf367edb1b405e3b0b260c8de5dd572699b7bafc
|
7
|
+
data.tar.gz: fd692ae67a1695ee4598b24d3cd030a7f091deb42f30df0f92085b3c54f765960c0b5eabaace7b306fadf8539b514fe77076cff825c27a292c5e36f56916646b
|
data/.github/workflows/main.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,35 @@
|
|
1
1
|
## Next Release
|
2
2
|
|
3
|
+
## 7.0.7 (03/01/2023)
|
4
|
+
|
5
|
+
* Check `Rails.application.config.content_security_policy` before insert `Bullet::Rack`
|
6
|
+
|
7
|
+
## 7.0.6 (03/01/2023)
|
8
|
+
|
9
|
+
* Better way to check if `ActionDispatch::ContentSecurityPolicy::Middleware` exists
|
10
|
+
|
11
|
+
## 7.0.5 (01/01/2023)
|
12
|
+
|
13
|
+
* Fix n+1 false positives in AR 7.0
|
14
|
+
* Fix eager_load nested has_many :through false positives
|
15
|
+
* Respect Content-Security-Policy nonces
|
16
|
+
* Added CallStacks support for avoid eager loading
|
17
|
+
* Iterate fewer times over objects
|
18
|
+
|
19
|
+
## 7.0.4 (11/28/2022)
|
20
|
+
|
21
|
+
* Fix `eager_load` `has_many :through` false positives
|
22
|
+
* mongoid7x: add dynamic methods
|
23
|
+
|
24
|
+
## 7.0.3 (08/13/2022)
|
25
|
+
|
26
|
+
* Replace `Array()` with `Array.wrap()`
|
27
|
+
|
28
|
+
## 7.0.2 (05/31/2022)
|
29
|
+
|
30
|
+
* Drop growl support
|
31
|
+
* Do not check html tag in Bullet::Rack anymore
|
32
|
+
|
3
33
|
## 7.0.1 (01/15/2022)
|
4
34
|
|
5
35
|
* Get rid of *_whitelist methods
|
data/MIT-LICENSE
CHANGED
data/README.md
CHANGED
@@ -49,7 +49,7 @@ mongoid.
|
|
49
49
|
|
50
50
|
## Configuration
|
51
51
|
|
52
|
-
Bullet won't
|
52
|
+
Bullet won't enable any notification systems unless you tell it to explicitly. Append to
|
53
53
|
`config/environments/development.rb` initializer with the following code:
|
54
54
|
|
55
55
|
```ruby
|
@@ -59,7 +59,6 @@ config.after_initialize do
|
|
59
59
|
Bullet.alert = true
|
60
60
|
Bullet.bullet_logger = true
|
61
61
|
Bullet.console = true
|
62
|
-
Bullet.growl = true
|
63
62
|
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
|
64
63
|
:password => 'bullets_password_for_jabber',
|
65
64
|
:receiver => 'your_account@jabber.org',
|
@@ -85,7 +84,6 @@ The code above will enable all of the Bullet notification systems:
|
|
85
84
|
* `Bullet.alert`: pop up a JavaScript alert in the browser
|
86
85
|
* `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)
|
87
86
|
* `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
|
88
|
-
* `Bullet.growl`: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
|
89
87
|
* `Bullet.xmpp`: send XMPP/Jabber notifications to the receiver indicated. Note that the code will currently not handle the adding of contacts, so you will need to make both accounts indicated know each other manually before you will receive any notifications. If you restart the development server frequently, the 'coming online' sound for the Bullet account may start to annoy - in this case set :show_online_status to false; you will still get notifications, but the Bullet account won't announce it's online status anymore.
|
90
88
|
* `Bullet.rails_logger`: add warnings directly to the Rails log
|
91
89
|
* `Bullet.honeybadger`: add notifications to Honeybadger
|
@@ -156,25 +154,26 @@ The Bullet log `log/bullet.log` will look something like this:
|
|
156
154
|
* N+1 Query:
|
157
155
|
|
158
156
|
```
|
159
|
-
2009-08-25 20:40:17[INFO]
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
|
164
|
-
/Users/richard/Downloads/test/app/
|
165
|
-
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
|
157
|
+
2009-08-25 20:40:17[INFO] USE eager loading detected:
|
158
|
+
Post => [:comments]·
|
159
|
+
Add to your query: .includes([:comments])
|
160
|
+
2009-08-25 20:40:17[INFO] Call stack
|
161
|
+
/Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
|
162
|
+
/Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
|
166
163
|
```
|
167
164
|
|
168
|
-
The first
|
165
|
+
The first log entry is a notification that N+1 queries have been encountered. The remaining entry is a stack trace so you can find exactly where the queries were invoked in your code, and fix them.
|
169
166
|
|
170
167
|
* Unused eager loading:
|
171
168
|
|
172
169
|
```
|
173
|
-
2009-08-25 20:53:56[INFO]
|
174
|
-
|
170
|
+
2009-08-25 20:53:56[INFO] AVOID eager loading detected
|
171
|
+
Post => [:comments]·
|
172
|
+
Remove from your query: .includes([:comments])
|
173
|
+
2009-08-25 20:53:56[INFO] Call stack
|
175
174
|
```
|
176
175
|
|
177
|
-
These
|
176
|
+
These lines are notifications that unused eager loadings have been encountered.
|
178
177
|
|
179
178
|
* Need counter cache:
|
180
179
|
|
@@ -183,10 +182,14 @@ These two lines are notifications that unused eager loadings have been encounter
|
|
183
182
|
Post => [:comments]
|
184
183
|
```
|
185
184
|
|
186
|
-
##
|
185
|
+
## XMPP/Jabber and Airbrake Support
|
187
186
|
|
188
187
|
see [https://github.com/flyerhzm/uniform_notifier](https://github.com/flyerhzm/uniform_notifier)
|
189
188
|
|
189
|
+
## Growl Support
|
190
|
+
|
191
|
+
Growl support is dropped from uniform_notifier 1.16.0, if you still want it, please use uniform_notifier 1.15.0.
|
192
|
+
|
190
193
|
## Important
|
191
194
|
|
192
195
|
If you find Bullet does not work for you, *please disable your browser's cache*.
|
@@ -219,7 +222,7 @@ end
|
|
219
222
|
|
220
223
|
### Work with sinatra
|
221
224
|
|
222
|
-
Configure and use `Bullet::Rack
|
225
|
+
Configure and use `Bullet::Rack`.
|
223
226
|
|
224
227
|
```ruby
|
225
228
|
configure :development do
|
@@ -229,6 +232,8 @@ configure :development do
|
|
229
232
|
end
|
230
233
|
```
|
231
234
|
|
235
|
+
If your application generates a Content-Security-Policy via a separate middleware, ensure that `Bullet::Rack` is loaded _before_ that middleware.
|
236
|
+
|
232
237
|
### Run in tests
|
233
238
|
|
234
239
|
First you need to enable Bullet in test environment.
|
@@ -282,7 +287,7 @@ $ rails g scaffold comment name:string post_id:integer
|
|
282
287
|
$ bundle exec rake db:migrate
|
283
288
|
```
|
284
289
|
|
285
|
-
2\. Change `app/
|
290
|
+
2\. Change `app/models/post.rb` and `app/models/comment.rb`
|
286
291
|
|
287
292
|
```ruby
|
288
293
|
class Post < ActiveRecord::Base
|
@@ -177,16 +177,18 @@ module Bullet
|
|
177
177
|
if Bullet.start?
|
178
178
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
179
179
|
refl = reflection.through_reflection
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
180
|
+
association = owner.association(refl.name)
|
181
|
+
if association.loaded?
|
182
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
|
183
|
+
Array.wrap(association.target).each do |through_record|
|
184
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
185
|
+
end
|
185
186
|
|
186
|
-
|
187
|
-
|
187
|
+
if refl.through_reflection?
|
188
|
+
refl = refl.through_reflection while refl.through_reflection?
|
188
189
|
|
189
|
-
|
190
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
|
191
|
+
end
|
190
192
|
end
|
191
193
|
end
|
192
194
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
@@ -150,14 +150,16 @@ module Bullet
|
|
150
150
|
|
151
151
|
if Bullet.start?
|
152
152
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
153
|
+
association = owner.association(reflection.through_reflection.name)
|
154
|
+
if association.loaded?
|
155
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
156
|
+
Array.wrap(association.target).each do |through_record|
|
157
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
158
|
+
end
|
158
159
|
|
159
|
-
|
160
|
-
|
160
|
+
if reflection.through_reflection != through_reflection
|
161
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
162
|
+
end
|
161
163
|
end
|
162
164
|
end
|
163
165
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
@@ -199,7 +201,7 @@ module Bullet
|
|
199
201
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
200
202
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
201
203
|
association = owner.association reflection.through_reflection.name
|
202
|
-
Array(association.target).each do |through_record|
|
204
|
+
Array.wrap(association.target).each do |through_record|
|
203
205
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
204
206
|
end
|
205
207
|
|
@@ -177,14 +177,16 @@ module Bullet
|
|
177
177
|
|
178
178
|
if Bullet.start?
|
179
179
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
180
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
181
180
|
association = owner.association(reflection.through_reflection.name)
|
182
|
-
|
183
|
-
Bullet::Detector::NPlusOneQuery.call_association(
|
184
|
-
|
181
|
+
if association.loaded?
|
182
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
183
|
+
Array.wrap(association.target).each do |through_record|
|
184
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
185
|
+
end
|
185
186
|
|
186
|
-
|
187
|
-
|
187
|
+
if reflection.through_reflection != through_reflection
|
188
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
189
|
+
end
|
188
190
|
end
|
189
191
|
end
|
190
192
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
@@ -226,7 +228,7 @@ module Bullet
|
|
226
228
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
227
229
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
228
230
|
association = owner.association(reflection.through_reflection.name)
|
229
|
-
Array(association.target).each do |through_record|
|
231
|
+
Array.wrap(association.target).each do |through_record|
|
230
232
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
231
233
|
end
|
232
234
|
|
@@ -177,14 +177,16 @@ module Bullet
|
|
177
177
|
|
178
178
|
if Bullet.start?
|
179
179
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
180
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
181
180
|
association = owner.association(reflection.through_reflection.name)
|
182
|
-
|
183
|
-
Bullet::Detector::NPlusOneQuery.call_association(
|
184
|
-
|
181
|
+
if association.loaded?
|
182
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
183
|
+
Array.wrap(association.target).each do |through_record|
|
184
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
185
|
+
end
|
185
186
|
|
186
|
-
|
187
|
-
|
187
|
+
if reflection.through_reflection != through_reflection
|
188
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
189
|
+
end
|
188
190
|
end
|
189
191
|
end
|
190
192
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
@@ -226,7 +228,7 @@ module Bullet
|
|
226
228
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
227
229
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
228
230
|
association = owner.association(reflection.through_reflection.name)
|
229
|
-
Array(association.target).each do |through_record|
|
231
|
+
Array.wrap(association.target).each do |through_record|
|
230
232
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
231
233
|
end
|
232
234
|
|
@@ -170,6 +170,13 @@ module Bullet
|
|
170
170
|
end
|
171
171
|
super
|
172
172
|
end
|
173
|
+
|
174
|
+
def inversed_from_queries(record)
|
175
|
+
if Bullet.start? && inversable?(record)
|
176
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
177
|
+
end
|
178
|
+
super
|
179
|
+
end
|
173
180
|
end
|
174
181
|
)
|
175
182
|
|
@@ -180,14 +187,16 @@ module Bullet
|
|
180
187
|
|
181
188
|
if Bullet.start?
|
182
189
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
183
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
184
190
|
association = owner.association(reflection.through_reflection.name)
|
185
|
-
|
186
|
-
Bullet::Detector::NPlusOneQuery.call_association(
|
187
|
-
|
191
|
+
if association.loaded?
|
192
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
193
|
+
Array.wrap(association.target).each do |through_record|
|
194
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
195
|
+
end
|
188
196
|
|
189
|
-
|
190
|
-
|
197
|
+
if reflection.through_reflection != through_reflection
|
198
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
199
|
+
end
|
191
200
|
end
|
192
201
|
end
|
193
202
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
@@ -229,7 +238,7 @@ module Bullet
|
|
229
238
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
230
239
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
231
240
|
association = owner.association(reflection.through_reflection.name)
|
232
|
-
Array(association.target).each do |through_record|
|
241
|
+
Array.wrap(association.target).each do |through_record|
|
233
242
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
234
243
|
end
|
235
244
|
|
data/lib/bullet/bullet_xhr.js
CHANGED
@@ -20,7 +20,7 @@
|
|
20
20
|
if (this.onload) {
|
21
21
|
this._storedOnload = this.onload;
|
22
22
|
}
|
23
|
-
this.onload = null
|
23
|
+
this.onload = null;
|
24
24
|
this.addEventListener("load", bulletXHROnload);
|
25
25
|
return Reflect.apply(oldSend, this, arguments);
|
26
26
|
}
|
@@ -31,7 +31,7 @@
|
|
31
31
|
) {
|
32
32
|
var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
|
33
33
|
if (bulletFooterText) {
|
34
|
-
setTimeout(function() {
|
34
|
+
setTimeout(function () {
|
35
35
|
var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
|
36
36
|
var header = oldHtml[0];
|
37
37
|
oldHtml = oldHtml.slice(1, oldHtml.length);
|
@@ -42,7 +42,7 @@
|
|
42
42
|
}
|
43
43
|
var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
|
44
44
|
if (bulletConsoleText && typeof console !== "undefined" && console.log) {
|
45
|
-
setTimeout(function() {
|
45
|
+
setTimeout(function () {
|
46
46
|
JSON.parse(bulletConsoleText).forEach((message) => {
|
47
47
|
if (console.groupCollapsed && console.groupEnd) {
|
48
48
|
console.groupCollapsed("Uniform Notifier");
|
@@ -13,6 +13,7 @@ module Bullet
|
|
13
13
|
'Detector::Association#add_object_associations',
|
14
14
|
"object: #{object.bullet_key}, associations: #{associations}"
|
15
15
|
)
|
16
|
+
call_stacks.add(object.bullet_key)
|
16
17
|
object_associations.add(object.bullet_key, associations)
|
17
18
|
end
|
18
19
|
|
@@ -25,6 +26,7 @@ module Bullet
|
|
25
26
|
'Detector::Association#add_call_object_associations',
|
26
27
|
"object: #{object.bullet_key}, associations: #{associations}"
|
27
28
|
)
|
29
|
+
call_stacks.add(object.bullet_key)
|
28
30
|
call_object_associations.add(object.bullet_key, associations)
|
29
31
|
end
|
30
32
|
|
@@ -76,6 +78,12 @@ module Bullet
|
|
76
78
|
def eager_loadings
|
77
79
|
Thread.current[:bullet_eager_loadings]
|
78
80
|
end
|
81
|
+
|
82
|
+
# cal_stacks keeps stacktraces where querie-objects were called from.
|
83
|
+
# e.g. { 'Object:111' => [SomeProject/app/controllers/...] }
|
84
|
+
def call_stacks
|
85
|
+
Thread.current[:bullet_call_stacks]
|
86
|
+
end
|
79
87
|
end
|
80
88
|
end
|
81
89
|
end
|
@@ -20,7 +20,7 @@ module Bullet
|
|
20
20
|
return unless Bullet.start?
|
21
21
|
return unless Bullet.counter_cache_enable?
|
22
22
|
|
23
|
-
objects = Array(object_or_objects)
|
23
|
+
objects = Array.wrap(object_or_objects)
|
24
24
|
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
25
25
|
|
26
26
|
Bullet.debug(
|
@@ -54,7 +54,7 @@ module Bullet
|
|
54
54
|
private
|
55
55
|
|
56
56
|
def create_notification(klazz, associations)
|
57
|
-
notify_associations = Array(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
|
57
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
|
58
58
|
|
59
59
|
if notify_associations.present?
|
60
60
|
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
|
@@ -7,7 +7,7 @@ module Bullet
|
|
7
7
|
extend StackTraceFilter
|
8
8
|
|
9
9
|
class << self
|
10
|
-
# executed when object.
|
10
|
+
# executed when object.associations is called.
|
11
11
|
# first, it keeps this method call for object.association.
|
12
12
|
# then, it checks if this associations call is unpreload.
|
13
13
|
# if it is, keeps this unpreload associations and caller.
|
@@ -25,7 +25,7 @@ module Bullet
|
|
25
25
|
)
|
26
26
|
if !excluded_stacktrace_path? && conditions_met?(object, associations)
|
27
27
|
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
|
28
|
-
create_notification caller_in_project, object.class.to_s, associations
|
28
|
+
create_notification caller_in_project(object.bullet_key), object.class.to_s, associations
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -33,15 +33,26 @@ module Bullet
|
|
33
33
|
return unless Bullet.start?
|
34
34
|
return unless Bullet.n_plus_one_query_enable?
|
35
35
|
|
36
|
-
objects = Array(object_or_objects)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
objects = Array.wrap(object_or_objects)
|
37
|
+
class_names_match_regex = true
|
38
|
+
primary_key_values_are_empty = true
|
39
|
+
keys_joined = ""
|
40
|
+
objects.each do |obj|
|
41
|
+
unless obj.class.name =~ /^HABTM_/
|
42
|
+
class_names_match_regex = false
|
43
|
+
end
|
44
|
+
unless obj.bullet_primary_key_value.nil?
|
45
|
+
primary_key_values_are_empty = false
|
46
|
+
end
|
47
|
+
keys_joined += "#{(keys_joined.empty?? '' : ', ')}#{obj.bullet_key}"
|
48
|
+
end
|
49
|
+
unless class_names_match_regex || primary_key_values_are_empty
|
50
|
+
Bullet.debug(
|
51
|
+
'Detector::NPlusOneQuery#add_possible_objects',
|
52
|
+
"objects: #{keys_joined}"
|
53
|
+
)
|
54
|
+
objects.each { |object| possible_objects.add object.bullet_key }
|
55
|
+
end
|
45
56
|
end
|
46
57
|
|
47
58
|
def add_impossible_object(object)
|
@@ -95,7 +106,7 @@ module Bullet
|
|
95
106
|
private
|
96
107
|
|
97
108
|
def create_notification(callers, klazz, associations)
|
98
|
-
notify_associations = Array(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
109
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
99
110
|
|
100
111
|
if notify_associations.present?
|
101
112
|
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
@@ -10,7 +10,7 @@ module Bullet
|
|
10
10
|
# check if there are unused preload associations.
|
11
11
|
# get related_objects from eager_loadings associated with object and associations
|
12
12
|
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
13
|
-
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload
|
13
|
+
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload associations
|
14
14
|
def check_unused_preload_associations
|
15
15
|
return unless Bullet.start?
|
16
16
|
return unless Bullet.unused_eager_loading_enable?
|
@@ -20,7 +20,7 @@ module Bullet
|
|
20
20
|
next if object_association_diff.empty?
|
21
21
|
|
22
22
|
Bullet.debug('detect unused preload', "object: #{bullet_key}, associations: #{object_association_diff}")
|
23
|
-
create_notification(caller_in_project, bullet_key.bullet_class_name, object_association_diff)
|
23
|
+
create_notification(caller_in_project(bullet_key), bullet_key.bullet_class_name, object_association_diff)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -65,7 +65,7 @@ module Bullet
|
|
65
65
|
private
|
66
66
|
|
67
67
|
def create_notification(callers, klazz, associations)
|
68
|
-
notify_associations = Array(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
|
68
|
+
notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
|
69
69
|
|
70
70
|
if notify_associations.present?
|
71
71
|
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
|
data/lib/bullet/mongoid7x.rb
CHANGED
@@ -4,22 +4,20 @@ module Bullet
|
|
4
4
|
module Mongoid
|
5
5
|
def self.enable
|
6
6
|
require 'mongoid'
|
7
|
+
require 'rubygems'
|
7
8
|
::Mongoid::Contextual::Mongo.class_eval do
|
8
9
|
alias_method :origin_first, :first
|
9
10
|
alias_method :origin_last, :last
|
10
11
|
alias_method :origin_each, :each
|
11
12
|
alias_method :origin_eager_load, :eager_load
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
result = origin_last(opts)
|
21
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
22
|
-
result
|
14
|
+
%i[first last].each do |context|
|
15
|
+
default = Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('7.5') ? nil : {}
|
16
|
+
define_method(context) do |opts = default|
|
17
|
+
result = send(:"origin_#{context}", opts)
|
18
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
19
|
+
result
|
20
|
+
end
|
23
21
|
end
|
24
22
|
|
25
23
|
def each(&block)
|
data/lib/bullet/rack.rb
CHANGED
@@ -4,6 +4,8 @@ module Bullet
|
|
4
4
|
class Rack
|
5
5
|
include Dependency
|
6
6
|
|
7
|
+
NONCE_MATCHER = /script-src .*'nonce-(?<nonce>[A-Za-z0-9+\/]+={0,2})'/
|
8
|
+
|
7
9
|
def initialize(app)
|
8
10
|
@app = app
|
9
11
|
end
|
@@ -20,11 +22,15 @@ module Bullet
|
|
20
22
|
if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
21
23
|
if html_request?(headers, response)
|
22
24
|
response_body = response_body(response)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
response_body = append_to_html_body(response_body,
|
25
|
+
|
26
|
+
with_security_policy_nonce(headers) do |nonce|
|
27
|
+
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
28
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
29
|
+
if Bullet.add_footer && !Bullet.skip_http_headers
|
30
|
+
response_body = append_to_html_body(response_body, xhr_script(nonce))
|
31
|
+
end
|
27
32
|
end
|
33
|
+
|
28
34
|
headers['Content-Length'] = response_body.bytesize.to_s
|
29
35
|
elsif !Bullet.skip_http_headers
|
30
36
|
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
@@ -79,7 +85,7 @@ module Bullet
|
|
79
85
|
end
|
80
86
|
|
81
87
|
def html_request?(headers, response)
|
82
|
-
headers['Content-Type']&.include?('text/html')
|
88
|
+
headers['Content-Type']&.include?('text/html')
|
83
89
|
end
|
84
90
|
|
85
91
|
def response_body(response)
|
@@ -118,8 +124,34 @@ module Bullet
|
|
118
124
|
end
|
119
125
|
|
120
126
|
# Make footer work for XHR requests by appending data to the footer
|
121
|
-
def xhr_script
|
122
|
-
|
127
|
+
def xhr_script(nonce = nil)
|
128
|
+
script = File.read("#{__dir__}/bullet_xhr.js")
|
129
|
+
|
130
|
+
if nonce
|
131
|
+
"<script type='text/javascript' nonce='#{nonce}'>#{script}</script>"
|
132
|
+
else
|
133
|
+
"<script type='text/javascript'>#{script}</script>"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def with_security_policy_nonce(headers)
|
138
|
+
matched = (headers['Content-Security-Policy'] || '').match(NONCE_MATCHER)
|
139
|
+
nonce = matched[:nonce] if matched
|
140
|
+
|
141
|
+
if nonce
|
142
|
+
console_enabled = UniformNotifier.console
|
143
|
+
alert_enabled = UniformNotifier.alert
|
144
|
+
|
145
|
+
UniformNotifier.console = { attributes: { nonce: nonce } } if console_enabled
|
146
|
+
UniformNotifier.alert = { attributes: { nonce: nonce } } if alert_enabled
|
147
|
+
|
148
|
+
yield nonce
|
149
|
+
|
150
|
+
UniformNotifier.console = console_enabled
|
151
|
+
UniformNotifier.alert = alert_enabled
|
152
|
+
else
|
153
|
+
yield
|
154
|
+
end
|
123
155
|
end
|
124
156
|
end
|
125
157
|
end
|
data/lib/bullet/registry.rb
CHANGED
@@ -6,10 +6,11 @@ module Bullet
|
|
6
6
|
VENDOR_PATH = '/vendor'
|
7
7
|
IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
8
8
|
|
9
|
-
|
9
|
+
# @param bullet_key[String] - use this to get stored call stack from call_stacks object.
|
10
|
+
def caller_in_project(bullet_key = nil)
|
10
11
|
vendor_root = Bullet.app_root + VENDOR_PATH
|
11
12
|
bundler_path = Bundler.bundle_path.to_s
|
12
|
-
select_caller_locations do |location|
|
13
|
+
select_caller_locations(bullet_key) do |location|
|
13
14
|
caller_path = location_as_path(location)
|
14
15
|
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
15
16
|
!caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
|
@@ -50,15 +51,16 @@ module Bullet
|
|
50
51
|
end
|
51
52
|
|
52
53
|
def location_as_path(location)
|
54
|
+
return location if location.is_a?(String)
|
55
|
+
|
53
56
|
IS_RUBY_19 ? location : location.absolute_path.to_s
|
54
57
|
end
|
55
58
|
|
56
|
-
def select_caller_locations
|
57
|
-
if IS_RUBY_19
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
59
|
+
def select_caller_locations(bullet_key = nil)
|
60
|
+
return caller.select { |caller_path| yield caller_path } if IS_RUBY_19
|
61
|
+
|
62
|
+
call_stack = bullet_key ? call_stacks[bullet_key] : caller_locations
|
63
|
+
call_stack.select { |location| yield location }
|
62
64
|
end
|
63
65
|
end
|
64
66
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
@@ -23,7 +23,11 @@ module Bullet
|
|
23
23
|
if defined?(Rails::Railtie)
|
24
24
|
class BulletRailtie < Rails::Railtie
|
25
25
|
initializer 'bullet.configure_rails_initialization' do |app|
|
26
|
-
|
26
|
+
if defined?(ActionDispatch::ContentSecurityPolicy::Middleware) && Rails.application.config.content_security_policy
|
27
|
+
app.middleware.insert_before ActionDispatch::ContentSecurityPolicy::Middleware, Bullet::Rack
|
28
|
+
else
|
29
|
+
app.middleware.use Bullet::Rack
|
30
|
+
end
|
27
31
|
end
|
28
32
|
end
|
29
33
|
end
|
@@ -109,7 +113,7 @@ module Bullet
|
|
109
113
|
end
|
110
114
|
|
111
115
|
def get_safelist_associations(type, class_name)
|
112
|
-
Array(@safelist[type][class_name])
|
116
|
+
Array.wrap(@safelist[type][class_name])
|
113
117
|
end
|
114
118
|
|
115
119
|
def reset_safelist
|
@@ -144,6 +148,7 @@ module Bullet
|
|
144
148
|
Thread.current[:bullet_impossible_objects] = Bullet::Registry::Object.new
|
145
149
|
Thread.current[:bullet_inversed_objects] = Bullet::Registry::Base.new
|
146
150
|
Thread.current[:bullet_eager_loadings] = Bullet::Registry::Association.new
|
151
|
+
Thread.current[:bullet_call_stacks] = Bullet::Registry::CallStack.new
|
147
152
|
|
148
153
|
Thread.current[:bullet_counter_possible_objects] ||= Bullet::Registry::Object.new
|
149
154
|
Thread.current[:bullet_counter_impossible_objects] ||= Bullet::Registry::Object.new
|
@@ -39,7 +39,7 @@ module Bullet
|
|
39
39
|
|
40
40
|
it 'should be false if object, association pair is not existed' do
|
41
41
|
NPlusOneQuery.add_object_associations(@post, :association1)
|
42
|
-
expect(NPlusOneQuery.association?(@post, :
|
42
|
+
expect(NPlusOneQuery.association?(@post, :association2)).to eq false
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -127,38 +127,6 @@ module Bullet
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
-
context '.caller_in_project' do
|
131
|
-
it 'should include only paths that are in the project' do
|
132
|
-
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
133
|
-
not_in_project = OpenStruct.new(absolute_path: '/def/def.rb')
|
134
|
-
|
135
|
-
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, not_in_project])
|
136
|
-
expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
|
137
|
-
expect(NPlusOneQuery).to receive(:create_notification).with([in_project], 'Post', :association)
|
138
|
-
NPlusOneQuery.call_association(@post, :association)
|
139
|
-
end
|
140
|
-
|
141
|
-
context 'stacktrace_includes' do
|
142
|
-
before { Bullet.stacktrace_includes = ['def', /xyz/] }
|
143
|
-
after { Bullet.stacktrace_includes = nil }
|
144
|
-
|
145
|
-
it 'should include paths that are in the stacktrace_include list' do
|
146
|
-
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
147
|
-
included_gems = [OpenStruct.new(absolute_path: '/def/def.rb'), OpenStruct.new(absolute_path: 'xyz/xyz.rb')]
|
148
|
-
excluded_gem = OpenStruct.new(absolute_path: '/ghi/ghi.rb')
|
149
|
-
|
150
|
-
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, *included_gems, excluded_gem])
|
151
|
-
expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
|
152
|
-
expect(NPlusOneQuery).to receive(:create_notification).with(
|
153
|
-
[in_project, *included_gems],
|
154
|
-
'Post',
|
155
|
-
:association
|
156
|
-
)
|
157
|
-
NPlusOneQuery.call_association(@post, :association)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
130
|
context '.add_possible_objects' do
|
163
131
|
it 'should add possible objects' do
|
164
132
|
NPlusOneQuery.add_possible_objects([@post, @post2])
|
@@ -65,6 +65,11 @@ module Bullet
|
|
65
65
|
expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
|
66
66
|
UnusedEagerLoading.check_unused_preload_associations
|
67
67
|
end
|
68
|
+
|
69
|
+
it 'should create call stack for notification' do
|
70
|
+
UnusedEagerLoading.add_object_associations(@post, :association)
|
71
|
+
expect(UnusedEagerLoading.send(:call_stacks).registry).not_to be_empty
|
72
|
+
end
|
68
73
|
end
|
69
74
|
|
70
75
|
context '.add_eager_loadings' do
|
@@ -74,8 +74,8 @@ module Bullet
|
|
74
74
|
it 'should send full_notice to notifier' do
|
75
75
|
notifier = double
|
76
76
|
allow(subject).to receive(:notifier).and_return(notifier)
|
77
|
-
allow(subject).to receive(:notification_data).and_return(foo: :bar)
|
78
|
-
expect(notifier).to receive(:inline_notify).with(foo: :bar)
|
77
|
+
allow(subject).to receive(:notification_data).and_return({ foo: :bar })
|
78
|
+
expect(notifier).to receive(:inline_notify).with({ foo: :bar })
|
79
79
|
subject.notify_inline
|
80
80
|
end
|
81
81
|
end
|
@@ -84,8 +84,8 @@ module Bullet
|
|
84
84
|
it 'should send full_out_of_channel to notifier' do
|
85
85
|
notifier = double
|
86
86
|
allow(subject).to receive(:notifier).and_return(notifier)
|
87
|
-
allow(subject).to receive(:notification_data).and_return(foo: :bar)
|
88
|
-
expect(notifier).to receive(:out_of_channel_notify).with(foo: :bar)
|
87
|
+
allow(subject).to receive(:notification_data).and_return({ foo: :bar })
|
88
|
+
expect(notifier).to receive(:out_of_channel_notify).with({ foo: :bar })
|
89
89
|
subject.notify_out_of_channel
|
90
90
|
end
|
91
91
|
end
|
data/spec/bullet/rack_spec.rb
CHANGED
@@ -31,12 +31,6 @@ module Bullet
|
|
31
31
|
response = double(body: '<html><head></head><body></body></html>')
|
32
32
|
expect(middleware).not_to be_html_request(headers, response)
|
33
33
|
end
|
34
|
-
|
35
|
-
it "should be false if response body doesn't contain html tag" do
|
36
|
-
headers = { 'Content-Type' => 'text/html' }
|
37
|
-
response = double(body: '<div>Partial</div>')
|
38
|
-
expect(middleware).not_to be_html_request(headers, response)
|
39
|
-
end
|
40
34
|
end
|
41
35
|
|
42
36
|
context 'empty?' do
|
@@ -56,7 +50,7 @@ module Bullet
|
|
56
50
|
end
|
57
51
|
|
58
52
|
it 'should be true if no response body' do
|
59
|
-
response = double
|
53
|
+
response = double
|
60
54
|
expect(middleware).to be_empty(response)
|
61
55
|
end
|
62
56
|
end
|
@@ -135,6 +129,23 @@ module Bullet
|
|
135
129
|
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
136
130
|
end
|
137
131
|
|
132
|
+
it 'should include CSP nonce in inline script if console_enabled and a CSP is applied' do
|
133
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
134
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
135
|
+
allow(middleware).to receive(:xhr_script).and_call_original
|
136
|
+
|
137
|
+
nonce = '+t9/wTlgG6xbHxXYUaDNzQ=='
|
138
|
+
app.headers = {
|
139
|
+
'Content-Type' => 'text/html',
|
140
|
+
'Content-Security-Policy' => "default-src 'self' https:; script-src 'self' https: 'nonce-#{nonce}'"
|
141
|
+
}
|
142
|
+
|
143
|
+
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
144
|
+
|
145
|
+
size = 56 + middleware.send(:footer_note).length + middleware.send(:xhr_script, nonce).length
|
146
|
+
expect(headers['Content-Length']).to eq(size.to_s)
|
147
|
+
end
|
148
|
+
|
138
149
|
it 'should change response body for html safe string if console_enabled is true' do
|
139
150
|
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
140
151
|
app.response =
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
module Bullet
|
6
|
+
RSpec.describe StackTraceFilter do
|
7
|
+
let(:dummy_class) { Class.new { extend StackTraceFilter } }
|
8
|
+
let(:root_path) { Dir.pwd }
|
9
|
+
let(:bundler_path) { Bundler.bundle_path }
|
10
|
+
|
11
|
+
describe '#caller_in_project' do
|
12
|
+
it 'gets the caller in the project' do
|
13
|
+
expect(dummy_class).to receive(:call_stacks).and_return({
|
14
|
+
'Post:1' => [
|
15
|
+
File.join(root_path, 'lib/bullet.rb'),
|
16
|
+
File.join(root_path, 'vendor/uniform_notifier.rb'),
|
17
|
+
File.join(bundler_path, 'rack.rb')
|
18
|
+
]
|
19
|
+
})
|
20
|
+
expect(dummy_class.caller_in_project('Post:1')).to eq([
|
21
|
+
File.join(root_path, 'lib/bullet.rb')
|
22
|
+
])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -58,6 +58,19 @@ if active_record?
|
|
58
58
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
59
59
|
end
|
60
60
|
|
61
|
+
it 'should detect non preload comment => post with inverse_of from a query' do
|
62
|
+
Post.first.comments.find_each do |comment|
|
63
|
+
comment.name
|
64
|
+
comment.post.name
|
65
|
+
end
|
66
|
+
|
67
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
68
|
+
expect(Post.first.comments.count).not_to eq(0)
|
69
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
70
|
+
|
71
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
72
|
+
end
|
73
|
+
|
61
74
|
it 'should detect non preload post => comments with empty?' do
|
62
75
|
Post.all.each { |post| post.comments.empty? }
|
63
76
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
@@ -474,7 +487,15 @@ if active_record?
|
|
474
487
|
end
|
475
488
|
|
476
489
|
it 'should detect preload associations' do
|
477
|
-
Firm.
|
490
|
+
Firm.preload(:clients).each { |firm| firm.clients.map(&:name) }
|
491
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
492
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
493
|
+
|
494
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
495
|
+
end
|
496
|
+
|
497
|
+
it 'should detect eager load association' do
|
498
|
+
Firm.eager_load(:clients).each { |firm| firm.clients.map(&:name) }
|
478
499
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
479
500
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
480
501
|
|
@@ -508,7 +529,15 @@ if active_record?
|
|
508
529
|
end
|
509
530
|
|
510
531
|
it 'should detect preload associations' do
|
511
|
-
Firm.
|
532
|
+
Firm.preload(:groups).each { |firm| firm.groups.map(&:name) }
|
533
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
534
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
535
|
+
|
536
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
537
|
+
end
|
538
|
+
|
539
|
+
it 'should detect eager load associations' do
|
540
|
+
Firm.eager_load(:groups).each { |firm| firm.groups.map(&:name) }
|
512
541
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
513
542
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
514
543
|
|
data/spec/support/sqlite_seed.rb
CHANGED
@@ -92,7 +92,7 @@ module Support
|
|
92
92
|
page3 = Page.create(name: 'page3', parent_id: folder2.id, author_id: author2.id)
|
93
93
|
page4 = Page.create(name: 'page4', parent_id: folder2.id, author_id: author2.id)
|
94
94
|
|
95
|
-
role1 = Role.create(name: '
|
95
|
+
role1 = Role.create(name: 'Admin')
|
96
96
|
role2 = Role.create(name: 'User')
|
97
97
|
|
98
98
|
user1 = User.create(name: 'user1', category: category1)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.0.
|
4
|
+
version: 7.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- lib/bullet/registry.rb
|
105
105
|
- lib/bullet/registry/association.rb
|
106
106
|
- lib/bullet/registry/base.rb
|
107
|
+
- lib/bullet/registry/call_stack.rb
|
107
108
|
- lib/bullet/registry/object.rb
|
108
109
|
- lib/bullet/stack_trace_filter.rb
|
109
110
|
- lib/bullet/version.rb
|
@@ -126,6 +127,7 @@ files:
|
|
126
127
|
- spec/bullet/registry/association_spec.rb
|
127
128
|
- spec/bullet/registry/base_spec.rb
|
128
129
|
- spec/bullet/registry/object_spec.rb
|
130
|
+
- spec/bullet/stack_trace_filter_spec.rb
|
129
131
|
- spec/bullet_spec.rb
|
130
132
|
- spec/integration/active_record/association_spec.rb
|
131
133
|
- spec/integration/counter_cache_spec.rb
|
@@ -195,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
195
197
|
- !ruby/object:Gem::Version
|
196
198
|
version: 1.3.6
|
197
199
|
requirements: []
|
198
|
-
rubygems_version: 3.
|
200
|
+
rubygems_version: 3.4.1
|
199
201
|
signing_key:
|
200
202
|
specification_version: 4
|
201
203
|
summary: help to kill N+1 queries and unused eager loading.
|
@@ -216,6 +218,7 @@ test_files:
|
|
216
218
|
- spec/bullet/registry/association_spec.rb
|
217
219
|
- spec/bullet/registry/base_spec.rb
|
218
220
|
- spec/bullet/registry/object_spec.rb
|
221
|
+
- spec/bullet/stack_trace_filter_spec.rb
|
219
222
|
- spec/bullet_spec.rb
|
220
223
|
- spec/integration/active_record/association_spec.rb
|
221
224
|
- spec/integration/counter_cache_spec.rb
|