bullet 6.1.0 → 7.0.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/.github/workflows/main.yml +82 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile.rails-6.0 +1 -1
- data/Gemfile.rails-6.1 +15 -0
- data/Gemfile.rails-7.0 +10 -0
- data/README.md +17 -7
- data/lib/bullet/active_job.rb +5 -1
- data/lib/bullet/active_record41.rb +1 -0
- data/lib/bullet/active_record42.rb +1 -0
- data/lib/bullet/active_record52.rb +22 -17
- data/lib/bullet/active_record60.rb +22 -17
- data/lib/bullet/active_record61.rb +272 -0
- data/lib/bullet/active_record70.rb +261 -0
- data/lib/bullet/bullet_xhr.js +18 -17
- data/lib/bullet/dependency.rb +16 -0
- data/lib/bullet/detector/base.rb +2 -1
- data/lib/bullet/detector/counter_cache.rb +1 -1
- data/lib/bullet/detector/n_plus_one_query.rb +3 -3
- data/lib/bullet/detector/unused_eager_loading.rb +1 -1
- data/lib/bullet/mongoid4x.rb +1 -1
- data/lib/bullet/mongoid5x.rb +1 -1
- data/lib/bullet/mongoid6x.rb +1 -1
- data/lib/bullet/mongoid7x.rb +24 -7
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +26 -18
- data/lib/bullet/stack_trace_filter.rb +7 -9
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +69 -23
- data/lib/generators/bullet/install_generator.rb +23 -25
- data/perf/benchmark.rb +4 -1
- data/spec/bullet/detector/counter_cache_spec.rb +1 -1
- data/spec/bullet/detector/unused_eager_loading_spec.rb +10 -10
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
- data/spec/bullet/rack_spec.rb +130 -3
- data/spec/bullet_spec.rb +39 -10
- data/spec/integration/active_record/association_spec.rb +54 -10
- data/spec/integration/counter_cache_spec.rb +4 -4
- data/spec/integration/mongoid/association_spec.rb +4 -4
- data/spec/models/attachment.rb +5 -0
- data/spec/models/deal.rb +5 -0
- data/spec/models/folder.rb +2 -1
- data/spec/models/group.rb +2 -1
- data/spec/models/page.rb +2 -1
- data/spec/models/post.rb +2 -0
- data/spec/models/submission.rb +1 -0
- data/spec/models/user.rb +1 -0
- data/spec/models/writer.rb +2 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/support/mongo_seed.rb +1 -0
- data/spec/support/sqlite_seed.rb +20 -0
- data/test.sh +2 -0
- metadata +15 -7
- data/.travis.yml +0 -33
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module SaveWithBulletSupport
|
|
5
|
+
def _create_record(*)
|
|
6
|
+
super do
|
|
7
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
|
8
|
+
yield(self) if block_given?
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ActiveRecord
|
|
14
|
+
def self.enable
|
|
15
|
+
require 'active_record'
|
|
16
|
+
::ActiveRecord::Base.extend(
|
|
17
|
+
Module.new do
|
|
18
|
+
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
|
19
|
+
result = super
|
|
20
|
+
if Bullet.start?
|
|
21
|
+
if result.is_a? Array
|
|
22
|
+
if result.size > 1
|
|
23
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
24
|
+
Bullet::Detector::CounterCache.add_possible_objects(result)
|
|
25
|
+
elsif result.size == 1
|
|
26
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
|
|
27
|
+
Bullet::Detector::CounterCache.add_impossible_object(result.first)
|
|
28
|
+
end
|
|
29
|
+
elsif result.is_a? ::ActiveRecord::Base
|
|
30
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
|
|
31
|
+
Bullet::Detector::CounterCache.add_impossible_object(result)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
|
|
40
|
+
|
|
41
|
+
::ActiveRecord::Relation.prepend(
|
|
42
|
+
Module.new do
|
|
43
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
44
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
45
|
+
def records
|
|
46
|
+
result = super
|
|
47
|
+
if Bullet.start?
|
|
48
|
+
if result.first.class.name !~ /^HABTM_/
|
|
49
|
+
if result.size > 1
|
|
50
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
51
|
+
Bullet::Detector::CounterCache.add_possible_objects(result)
|
|
52
|
+
elsif result.size == 1
|
|
53
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
|
|
54
|
+
Bullet::Detector::CounterCache.add_impossible_object(result.first)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
result
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
::ActiveRecord::Associations::Preloader::Branch.prepend(
|
|
64
|
+
Module.new do
|
|
65
|
+
def preloaders_for_reflection(reflection, reflection_records)
|
|
66
|
+
if Bullet.start?
|
|
67
|
+
reflection_records.compact!
|
|
68
|
+
if reflection_records.first.class.name !~ /^HABTM_/
|
|
69
|
+
reflection_records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
|
|
70
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
|
|
79
|
+
Module.new do
|
|
80
|
+
def preloaded_records
|
|
81
|
+
if Bullet.start? && !defined?(@preloaded_records)
|
|
82
|
+
source_preloaders.each do |source_preloader|
|
|
83
|
+
reflection_name = source_preloader.send(:reflection).name
|
|
84
|
+
source_preloader.send(:owners).each do |owner|
|
|
85
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
super
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
::ActiveRecord::Associations::JoinDependency.prepend(
|
|
95
|
+
Module.new do
|
|
96
|
+
def instantiate(result_set, strict_loading_value, &block)
|
|
97
|
+
@bullet_eager_loadings = {}
|
|
98
|
+
records = super
|
|
99
|
+
|
|
100
|
+
if Bullet.start?
|
|
101
|
+
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
|
|
102
|
+
objects = eager_loadings_hash.keys
|
|
103
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
|
|
104
|
+
objects,
|
|
105
|
+
eager_loadings_hash[objects.first].to_a
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
records
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
|
113
|
+
if Bullet.start?
|
|
114
|
+
unless ar_parent.nil?
|
|
115
|
+
parent.children.each do |node|
|
|
116
|
+
key = aliases.column_alias(node, node.primary_key)
|
|
117
|
+
id = row[key]
|
|
118
|
+
next unless id.nil?
|
|
119
|
+
|
|
120
|
+
associations = node.reflection.name
|
|
121
|
+
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
|
122
|
+
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
|
123
|
+
@bullet_eager_loadings[ar_parent.class] ||= {}
|
|
124
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
|
|
125
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] << associations
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
super
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# call join associations
|
|
134
|
+
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
|
135
|
+
result = super
|
|
136
|
+
|
|
137
|
+
if Bullet.start?
|
|
138
|
+
associations = node.reflection.name
|
|
139
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
140
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
141
|
+
@bullet_eager_loadings[record.class] ||= {}
|
|
142
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
143
|
+
@bullet_eager_loadings[record.class][record] << associations
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
result
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
::ActiveRecord::Associations::Association.prepend(
|
|
152
|
+
Module.new do
|
|
153
|
+
def inversed_from(record)
|
|
154
|
+
if Bullet.start?
|
|
155
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
|
156
|
+
end
|
|
157
|
+
super
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
|
163
|
+
Module.new do
|
|
164
|
+
def load_target
|
|
165
|
+
records = super
|
|
166
|
+
|
|
167
|
+
if Bullet.start?
|
|
168
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
169
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
170
|
+
association = owner.association(reflection.through_reflection.name)
|
|
171
|
+
Array(association.target).each do |through_record|
|
|
172
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if reflection.through_reflection != through_reflection
|
|
176
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
180
|
+
if records.first.class.name !~ /^HABTM_/
|
|
181
|
+
if records.size > 1
|
|
182
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
183
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
184
|
+
elsif records.size == 1
|
|
185
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
186
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
records
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def empty?
|
|
194
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
|
195
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
196
|
+
end
|
|
197
|
+
super
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def include?(object)
|
|
201
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
|
|
202
|
+
super
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
::ActiveRecord::Associations::SingularAssociation.prepend(
|
|
208
|
+
Module.new do
|
|
209
|
+
# call has_one and belongs_to associations
|
|
210
|
+
def reader
|
|
211
|
+
result = super
|
|
212
|
+
|
|
213
|
+
if Bullet.start?
|
|
214
|
+
if owner.class.name !~ /^HABTM_/
|
|
215
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
|
216
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
|
217
|
+
association = owner.association(reflection.through_reflection.name)
|
|
218
|
+
Array(association.target).each do |through_record|
|
|
219
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
if reflection.through_reflection != through_reflection
|
|
223
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
227
|
+
|
|
228
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
|
229
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
230
|
+
else
|
|
231
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
result
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(
|
|
241
|
+
Module.new do
|
|
242
|
+
def empty?
|
|
243
|
+
result = super
|
|
244
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
|
245
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
246
|
+
end
|
|
247
|
+
result
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def count_records
|
|
251
|
+
result = reflection.has_cached_counter?
|
|
252
|
+
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
|
|
253
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
|
254
|
+
end
|
|
255
|
+
super
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
data/lib/bullet/bullet_xhr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function() {
|
|
1
|
+
(function () {
|
|
2
2
|
var oldOpen = window.XMLHttpRequest.prototype.open;
|
|
3
3
|
var oldSend = window.XMLHttpRequest.prototype.send;
|
|
4
4
|
|
|
@@ -10,41 +10,42 @@
|
|
|
10
10
|
if (isBulletInitiated()) return;
|
|
11
11
|
|
|
12
12
|
function isBulletInitiated() {
|
|
13
|
-
return oldOpen.name ==
|
|
13
|
+
return oldOpen.name == "bulletXHROpen" && oldSend.name == "bulletXHRSend";
|
|
14
14
|
}
|
|
15
15
|
function bulletXHROpen(_, url) {
|
|
16
16
|
this._storedUrl = url;
|
|
17
|
-
return
|
|
17
|
+
return Reflect.apply(oldOpen, this, arguments);
|
|
18
18
|
}
|
|
19
19
|
function bulletXHRSend() {
|
|
20
20
|
if (this.onload) {
|
|
21
21
|
this._storedOnload = this.onload;
|
|
22
22
|
}
|
|
23
|
-
this.
|
|
24
|
-
|
|
23
|
+
this.onload = null
|
|
24
|
+
this.addEventListener("load", bulletXHROnload);
|
|
25
|
+
return Reflect.apply(oldSend, this, arguments);
|
|
25
26
|
}
|
|
26
27
|
function bulletXHROnload() {
|
|
27
28
|
if (
|
|
28
|
-
this._storedUrl.startsWith(window.location.protocol +
|
|
29
|
-
!this._storedUrl.startsWith(
|
|
29
|
+
this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
|
|
30
|
+
!this._storedUrl.startsWith("http") // For relative paths
|
|
30
31
|
) {
|
|
31
|
-
var bulletFooterText = this.getResponseHeader(
|
|
32
|
+
var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
|
|
32
33
|
if (bulletFooterText) {
|
|
33
|
-
setTimeout(()
|
|
34
|
-
var oldHtml = document.
|
|
34
|
+
setTimeout(function() {
|
|
35
|
+
var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
|
|
35
36
|
var header = oldHtml[0];
|
|
36
37
|
oldHtml = oldHtml.slice(1, oldHtml.length);
|
|
37
38
|
var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
|
|
38
39
|
newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
|
|
39
|
-
document.
|
|
40
|
+
document.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
|
|
40
41
|
}, 0);
|
|
41
42
|
}
|
|
42
|
-
var bulletConsoleText = this.getResponseHeader(
|
|
43
|
-
if (bulletConsoleText && typeof console !==
|
|
44
|
-
setTimeout(()
|
|
45
|
-
JSON.parse(bulletConsoleText).forEach(message => {
|
|
43
|
+
var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
|
|
44
|
+
if (bulletConsoleText && typeof console !== "undefined" && console.log) {
|
|
45
|
+
setTimeout(function() {
|
|
46
|
+
JSON.parse(bulletConsoleText).forEach((message) => {
|
|
46
47
|
if (console.groupCollapsed && console.groupEnd) {
|
|
47
|
-
console.groupCollapsed(
|
|
48
|
+
console.groupCollapsed("Uniform Notifier");
|
|
48
49
|
console.log(message);
|
|
49
50
|
console.groupEnd();
|
|
50
51
|
} else {
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
if (this._storedOnload) {
|
|
58
|
-
return
|
|
59
|
+
return Reflect.apply(this._storedOnload, this, arguments);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
window.XMLHttpRequest.prototype.open = bulletXHROpen;
|
data/lib/bullet/dependency.rb
CHANGED
|
@@ -27,6 +27,10 @@ module Bullet
|
|
|
27
27
|
'active_record52'
|
|
28
28
|
elsif active_record60?
|
|
29
29
|
'active_record60'
|
|
30
|
+
elsif active_record61?
|
|
31
|
+
'active_record61'
|
|
32
|
+
elsif active_record70?
|
|
33
|
+
'active_record70'
|
|
30
34
|
else
|
|
31
35
|
raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
|
|
32
36
|
end
|
|
@@ -62,6 +66,10 @@ module Bullet
|
|
|
62
66
|
active_record? && ::ActiveRecord::VERSION::MAJOR == 6
|
|
63
67
|
end
|
|
64
68
|
|
|
69
|
+
def active_record7?
|
|
70
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR == 7
|
|
71
|
+
end
|
|
72
|
+
|
|
65
73
|
def active_record40?
|
|
66
74
|
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
|
|
67
75
|
end
|
|
@@ -90,6 +98,14 @@ module Bullet
|
|
|
90
98
|
active_record6? && ::ActiveRecord::VERSION::MINOR == 0
|
|
91
99
|
end
|
|
92
100
|
|
|
101
|
+
def active_record61?
|
|
102
|
+
active_record6? && ::ActiveRecord::VERSION::MINOR == 1
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def active_record70?
|
|
106
|
+
active_record7? && ::ActiveRecord::VERSION::MINOR == 0
|
|
107
|
+
end
|
|
108
|
+
|
|
93
109
|
def mongoid4x?
|
|
94
110
|
mongoid? && ::Mongoid::VERSION =~ /\A4/
|
|
95
111
|
end
|
data/lib/bullet/detector/base.rb
CHANGED
|
@@ -54,7 +54,7 @@ module Bullet
|
|
|
54
54
|
private
|
|
55
55
|
|
|
56
56
|
def create_notification(klazz, associations)
|
|
57
|
-
notify_associations = Array(associations) - Bullet.
|
|
57
|
+
notify_associations = Array(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
|
|
@@ -35,6 +35,7 @@ module Bullet
|
|
|
35
35
|
|
|
36
36
|
objects = Array(object_or_objects)
|
|
37
37
|
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
|
38
|
+
return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
|
|
38
39
|
|
|
39
40
|
Bullet.debug(
|
|
40
41
|
'Detector::NPlusOneQuery#add_possible_objects',
|
|
@@ -84,8 +85,7 @@ module Bullet
|
|
|
84
85
|
# associations == v comparison order is important here because
|
|
85
86
|
# v variable might be a squeel node where :== method is redefined,
|
|
86
87
|
# so it does not compare values at all and return unexpected results
|
|
87
|
-
result =
|
|
88
|
-
v.is_a?(Hash) ? v.key?(associations) : associations == v
|
|
88
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
|
89
89
|
return true if result
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -95,7 +95,7 @@ module Bullet
|
|
|
95
95
|
private
|
|
96
96
|
|
|
97
97
|
def create_notification(callers, klazz, associations)
|
|
98
|
-
notify_associations = Array(associations) - Bullet.
|
|
98
|
+
notify_associations = Array(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
|
99
99
|
|
|
100
100
|
if notify_associations.present?
|
|
101
101
|
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
|
@@ -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.
|
|
68
|
+
notify_associations = Array(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/mongoid4x.rb
CHANGED
data/lib/bullet/mongoid5x.rb
CHANGED
data/lib/bullet/mongoid6x.rb
CHANGED
data/lib/bullet/mongoid7x.rb
CHANGED
|
@@ -25,14 +25,31 @@ module Bullet
|
|
|
25
25
|
def each(&block)
|
|
26
26
|
return to_enum unless block_given?
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
first_document = nil
|
|
29
|
+
document_count = 0
|
|
30
|
+
|
|
31
|
+
origin_each do |document|
|
|
32
|
+
document_count += 1
|
|
33
|
+
|
|
34
|
+
if document_count == 1
|
|
35
|
+
first_document = document
|
|
36
|
+
elsif document_count == 2
|
|
37
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
|
|
38
|
+
yield(first_document)
|
|
39
|
+
first_document = nil
|
|
40
|
+
yield(document)
|
|
41
|
+
else
|
|
42
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
|
|
43
|
+
yield(document)
|
|
44
|
+
end
|
|
34
45
|
end
|
|
35
|
-
|
|
46
|
+
|
|
47
|
+
if document_count == 1
|
|
48
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
|
|
49
|
+
yield(first_document)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
self
|
|
36
53
|
end
|
|
37
54
|
|
|
38
55
|
def eager_load(docs)
|
data/lib/bullet/notification.rb
CHANGED
data/lib/bullet/rack.rb
CHANGED
|
@@ -17,14 +17,16 @@ module Bullet
|
|
|
17
17
|
response_body = nil
|
|
18
18
|
|
|
19
19
|
if Bullet.notification?
|
|
20
|
-
if
|
|
20
|
+
if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
|
21
21
|
if html_request?(headers, response)
|
|
22
22
|
response_body = response_body(response)
|
|
23
23
|
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
|
24
24
|
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
25
|
-
|
|
25
|
+
if Bullet.add_footer && !Bullet.skip_http_headers
|
|
26
|
+
response_body = append_to_html_body(response_body, xhr_script)
|
|
27
|
+
end
|
|
26
28
|
headers['Content-Length'] = response_body.bytesize.to_s
|
|
27
|
-
|
|
29
|
+
elsif !Bullet.skip_http_headers
|
|
28
30
|
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
|
29
31
|
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
|
30
32
|
end
|
|
@@ -46,6 +48,7 @@ module Bullet
|
|
|
46
48
|
|
|
47
49
|
def append_to_html_body(response_body, content)
|
|
48
50
|
body = response_body.dup
|
|
51
|
+
content = content.html_safe if content.respond_to?(:html_safe)
|
|
49
52
|
if body.include?('</body>')
|
|
50
53
|
position = body.rindex('</body>')
|
|
51
54
|
body.insert(position, content)
|
|
@@ -55,14 +58,14 @@ module Bullet
|
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def footer_note
|
|
58
|
-
"<
|
|
61
|
+
"<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
|
|
59
62
|
end
|
|
60
63
|
|
|
61
64
|
def set_header(headers, header_name, header_array)
|
|
62
65
|
# Many proxy applications such as Nginx and AWS ELB limit
|
|
63
66
|
# the size a header to 8KB, so truncate the list of reports to
|
|
64
67
|
# be under that limit
|
|
65
|
-
header_array.pop while header_array.to_json.length > 8 *
|
|
68
|
+
header_array.pop while header_array.to_json.length > 8 * 1024
|
|
66
69
|
headers[header_name] = header_array.to_json
|
|
67
70
|
end
|
|
68
71
|
|
|
@@ -81,30 +84,35 @@ module Bullet
|
|
|
81
84
|
def response_body(response)
|
|
82
85
|
if response.respond_to?(:body)
|
|
83
86
|
Array === response.body ? response.body.first : response.body
|
|
84
|
-
|
|
87
|
+
elsif response.respond_to?(:first)
|
|
85
88
|
response.first
|
|
86
89
|
end
|
|
87
90
|
end
|
|
88
91
|
|
|
89
92
|
private
|
|
90
93
|
|
|
91
|
-
def
|
|
94
|
+
def details_attributes
|
|
95
|
+
<<~EOF
|
|
96
|
+
id="bullet-footer" data-is-bullet-footer
|
|
97
|
+
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;"
|
|
98
|
+
EOF
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def summary_attributes
|
|
92
102
|
<<~EOF
|
|
93
|
-
|
|
94
|
-
-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
|
|
95
|
-
-moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
|
|
96
|
-
padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
|
|
97
|
-
color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
|
|
103
|
+
style="font-weight: 600; padding: 2px 8px"
|
|
98
104
|
EOF
|
|
99
105
|
end
|
|
100
106
|
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
def footer_content_attributes
|
|
108
|
+
<<~EOF
|
|
109
|
+
style="padding: 8px; border-top: 1px solid #9b1c1c;"
|
|
110
|
+
EOF
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def footer_console_message
|
|
104
114
|
if Bullet.console_enabled?
|
|
105
|
-
"<span>See 'Uniform Notifier' in JS Console for Stacktrace</span
|
|
106
|
-
else
|
|
107
|
-
cancel_button
|
|
115
|
+
"<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
|
|
108
116
|
end
|
|
109
117
|
end
|
|
110
118
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require "bundler"
|
|
2
3
|
|
|
3
4
|
module Bullet
|
|
4
5
|
module StackTraceFilter
|
|
5
6
|
VENDOR_PATH = '/vendor'
|
|
7
|
+
IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
|
6
8
|
|
|
7
9
|
def caller_in_project
|
|
8
10
|
vendor_root = Bullet.app_root + VENDOR_PATH
|
|
@@ -10,8 +12,9 @@ module Bullet
|
|
|
10
12
|
select_caller_locations do |location|
|
|
11
13
|
caller_path = location_as_path(location)
|
|
12
14
|
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
|
13
|
-
!caller_path.include?(bundler_path) ||
|
|
14
|
-
|
|
15
|
+
!caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
|
|
16
|
+
pattern_matches?(location, include_pattern)
|
|
17
|
+
}
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
|
|
@@ -47,20 +50,15 @@ module Bullet
|
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
def location_as_path(location)
|
|
50
|
-
|
|
53
|
+
IS_RUBY_19 ? location : location.absolute_path.to_s
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def select_caller_locations
|
|
54
|
-
if
|
|
57
|
+
if IS_RUBY_19
|
|
55
58
|
caller.select { |caller_path| yield caller_path }
|
|
56
59
|
else
|
|
57
60
|
caller_locations.select { |location| yield location }
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
|
-
|
|
61
|
-
def ruby_19?
|
|
62
|
-
@ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') if @ruby_19.nil?
|
|
63
|
-
@ruby_19
|
|
64
|
-
end
|
|
65
63
|
end
|
|
66
64
|
end
|
data/lib/bullet/version.rb
CHANGED