bullet 6.0.0 → 6.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/.travis.yml +20 -1
- data/CHANGELOG.md +17 -1
- data/Gemfile.rails-6.0 +1 -1
- data/README.md +23 -9
- data/lib/bullet.rb +26 -16
- data/lib/bullet/active_job.rb +9 -0
- data/lib/bullet/active_record4.rb +9 -24
- data/lib/bullet/active_record41.rb +7 -19
- data/lib/bullet/active_record42.rb +8 -16
- data/lib/bullet/active_record5.rb +188 -170
- data/lib/bullet/active_record52.rb +176 -161
- data/lib/bullet/active_record60.rb +193 -171
- data/lib/bullet/bullet_xhr.js +20 -15
- data/lib/bullet/dependency.rb +36 -34
- data/lib/bullet/detector/association.rb +24 -18
- data/lib/bullet/detector/base.rb +1 -2
- data/lib/bullet/detector/counter_cache.rb +10 -6
- data/lib/bullet/detector/n_plus_one_query.rb +18 -8
- data/lib/bullet/detector/unused_eager_loading.rb +5 -2
- data/lib/bullet/mongoid4x.rb +2 -6
- data/lib/bullet/mongoid5x.rb +2 -6
- data/lib/bullet/mongoid6x.rb +2 -6
- data/lib/bullet/mongoid7x.rb +2 -6
- data/lib/bullet/notification/base.rb +14 -18
- data/lib/bullet/notification/n_plus_one_query.rb +2 -4
- data/lib/bullet/notification/unused_eager_loading.rb +2 -4
- data/lib/bullet/rack.rb +5 -3
- data/lib/bullet/stack_trace_filter.rb +5 -10
- data/lib/bullet/version.rb +1 -1
- data/lib/generators/bullet/install_generator.rb +4 -2
- data/perf/benchmark.rb +8 -14
- data/spec/bullet/detector/counter_cache_spec.rb +5 -5
- data/spec/bullet/detector/n_plus_one_query_spec.rb +7 -3
- data/spec/bullet/detector/unused_eager_loading_spec.rb +29 -12
- data/spec/bullet/notification/base_spec.rb +1 -3
- data/spec/bullet/notification/n_plus_one_query_spec.rb +18 -3
- data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
- data/spec/bullet/rack_spec.rb +20 -5
- data/spec/bullet/registry/association_spec.rb +2 -2
- data/spec/bullet/registry/base_spec.rb +1 -1
- data/spec/bullet_spec.rb +10 -29
- data/spec/integration/active_record/association_spec.rb +42 -122
- data/spec/integration/counter_cache_spec.rb +10 -30
- data/spec/integration/mongoid/association_spec.rb +18 -32
- data/spec/models/folder.rb +1 -2
- data/spec/models/group.rb +1 -2
- data/spec/models/page.rb +1 -2
- data/spec/models/writer.rb +1 -2
- data/spec/spec_helper.rb +6 -10
- data/spec/support/bullet_ext.rb +8 -9
- data/spec/support/mongo_seed.rb +2 -16
- metadata +3 -2
@@ -13,233 +13,255 @@ module Bullet
|
|
13
13
|
module ActiveRecord
|
14
14
|
def self.enable
|
15
15
|
require 'active_record'
|
16
|
-
::ActiveRecord::Base.extend(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
if
|
21
|
-
if result.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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)
|
27
32
|
end
|
28
|
-
elsif result.is_a? ::ActiveRecord::Base
|
29
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
|
30
|
-
Bullet::Detector::CounterCache.add_impossible_object(result)
|
31
33
|
end
|
34
|
+
result
|
32
35
|
end
|
33
|
-
result
|
34
36
|
end
|
35
|
-
|
37
|
+
)
|
36
38
|
|
37
39
|
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
|
38
40
|
|
39
|
-
::ActiveRecord::Relation.prepend(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
if
|
46
|
-
if result.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
52
56
|
end
|
53
57
|
end
|
58
|
+
result
|
54
59
|
end
|
55
|
-
result
|
56
60
|
end
|
57
|
-
|
58
|
-
|
59
|
-
::ActiveRecord::Associations::Preloader.prepend(
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
records.
|
65
|
-
Bullet::Detector::Association.add_object_associations(record, association)
|
61
|
+
)
|
62
|
+
|
63
|
+
::ActiveRecord::Associations::Preloader.prepend(
|
64
|
+
Module.new do
|
65
|
+
def preloaders_for_one(association, records, scope, polymorphic_parent)
|
66
|
+
if Bullet.start?
|
67
|
+
records.compact!
|
68
|
+
if records.first.class.name !~ /^HABTM_/
|
69
|
+
records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
|
70
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
66
71
|
end
|
67
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
68
72
|
end
|
73
|
+
super
|
69
74
|
end
|
70
|
-
super
|
71
|
-
end
|
72
75
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
Bullet::Detector::
|
76
|
+
def preloaders_for_reflection(reflection, records, scope)
|
77
|
+
if Bullet.start?
|
78
|
+
records.compact!
|
79
|
+
if records.first.class.name !~ /^HABTM_/
|
80
|
+
records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
|
81
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
|
79
82
|
end
|
80
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
|
81
83
|
end
|
84
|
+
super
|
82
85
|
end
|
83
|
-
super
|
84
86
|
end
|
85
|
-
|
86
|
-
|
87
|
-
::ActiveRecord::FinderMethods.prepend(Module.new do
|
88
|
-
# add includes in scope
|
89
|
-
def find_with_associations
|
90
|
-
return super { |r| yield r } if block_given?
|
87
|
+
)
|
91
88
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
89
|
+
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
|
90
|
+
Module.new do
|
91
|
+
def preloaded_records
|
92
|
+
if Bullet.start? && !defined?(@preloaded_records)
|
93
|
+
source_preloaders.each do |source_preloader|
|
94
|
+
reflection_name = source_preloader.send(:reflection).name
|
95
|
+
source_preloader.send(:owners).each do |owner|
|
96
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
97
|
+
end
|
98
|
+
end
|
97
99
|
end
|
98
|
-
|
100
|
+
super
|
99
101
|
end
|
100
|
-
records
|
101
102
|
end
|
102
|
-
|
103
|
+
)
|
103
104
|
|
104
|
-
::ActiveRecord::
|
105
|
-
|
106
|
-
|
107
|
-
|
105
|
+
::ActiveRecord::FinderMethods.prepend(
|
106
|
+
Module.new do
|
107
|
+
# add includes in scope
|
108
|
+
def find_with_associations
|
109
|
+
return super { |r| yield r } if block_given?
|
108
110
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
Bullet::Detector::
|
111
|
+
records = super
|
112
|
+
if Bullet.start?
|
113
|
+
associations = (eager_load_values + includes_values).uniq
|
114
|
+
records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
|
115
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
113
116
|
end
|
117
|
+
records
|
114
118
|
end
|
115
|
-
records
|
116
119
|
end
|
120
|
+
)
|
117
121
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
Bullet::Detector::
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
@bullet_eager_loadings[ar_parent.class][ar_parent] << associations
|
122
|
+
::ActiveRecord::Associations::JoinDependency.prepend(
|
123
|
+
Module.new do
|
124
|
+
def instantiate(result_set, &block)
|
125
|
+
@bullet_eager_loadings = {}
|
126
|
+
records = super
|
127
|
+
|
128
|
+
if Bullet.start?
|
129
|
+
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
|
130
|
+
objects = eager_loadings_hash.keys
|
131
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
|
132
|
+
objects,
|
133
|
+
eager_loadings_hash[objects.first].to_a
|
134
|
+
)
|
132
135
|
end
|
133
136
|
end
|
137
|
+
records
|
134
138
|
end
|
135
139
|
|
136
|
-
|
137
|
-
|
140
|
+
def construct(ar_parent, parent, row, seen, model_cache)
|
141
|
+
if Bullet.start?
|
142
|
+
unless ar_parent.nil?
|
143
|
+
parent.children.each do |node|
|
144
|
+
key = aliases.column_alias(node, node.primary_key)
|
145
|
+
id = row[key]
|
146
|
+
next unless id.nil?
|
138
147
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
148
|
+
associations = node.reflection.name
|
149
|
+
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
150
|
+
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
151
|
+
@bullet_eager_loadings[ar_parent.class] ||= {}
|
152
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
|
153
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] << associations
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
super
|
150
159
|
end
|
151
160
|
|
152
|
-
|
161
|
+
# call join associations
|
162
|
+
def construct_model(record, node, row, model_cache, id)
|
163
|
+
result = super
|
164
|
+
|
165
|
+
if Bullet.start?
|
166
|
+
associations = node.reflection.name
|
167
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
168
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
169
|
+
@bullet_eager_loadings[record.class] ||= {}
|
170
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
171
|
+
@bullet_eager_loadings[record.class][record] << associations
|
172
|
+
end
|
173
|
+
|
174
|
+
result
|
175
|
+
end
|
153
176
|
end
|
154
|
-
|
155
|
-
|
156
|
-
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
if Bullet.start?
|
161
|
-
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
162
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
163
|
-
association = owner.association reflection.through_reflection.name
|
164
|
-
Array(association.target).each do |through_record|
|
165
|
-
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
166
|
-
end
|
177
|
+
)
|
178
|
+
|
179
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
180
|
+
Module.new do
|
181
|
+
def load_target
|
182
|
+
records = super
|
167
183
|
|
168
|
-
|
169
|
-
|
184
|
+
if Bullet.start?
|
185
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
186
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
187
|
+
association = owner.association(reflection.through_reflection.name)
|
188
|
+
Array(association.target).each do |through_record|
|
189
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
190
|
+
end
|
191
|
+
|
192
|
+
if reflection.through_reflection != through_reflection
|
193
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
194
|
+
end
|
170
195
|
end
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
196
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
197
|
+
if records.first.class.name !~ /^HABTM_/
|
198
|
+
if records.size > 1
|
199
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
200
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
201
|
+
elsif records.size == 1
|
202
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
203
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
204
|
+
end
|
180
205
|
end
|
181
206
|
end
|
207
|
+
records
|
182
208
|
end
|
183
|
-
records
|
184
|
-
end
|
185
209
|
|
186
|
-
|
187
|
-
|
188
|
-
|
210
|
+
def empty?
|
211
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
212
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
213
|
+
end
|
214
|
+
super
|
189
215
|
end
|
190
|
-
super
|
191
|
-
end
|
192
216
|
|
193
|
-
|
194
|
-
|
195
|
-
|
217
|
+
def include?(object)
|
218
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
|
219
|
+
super
|
196
220
|
end
|
197
|
-
super
|
198
221
|
end
|
199
|
-
|
200
|
-
|
201
|
-
::ActiveRecord::Associations::SingularAssociation.prepend(
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
if
|
209
|
-
Bullet::Detector::NPlusOneQuery.
|
210
|
-
|
211
|
-
Bullet::Detector::NPlusOneQuery.
|
222
|
+
)
|
223
|
+
|
224
|
+
::ActiveRecord::Associations::SingularAssociation.prepend(
|
225
|
+
Module.new do
|
226
|
+
# call has_one and belongs_to associations
|
227
|
+
def target
|
228
|
+
result = super()
|
229
|
+
|
230
|
+
if Bullet.start?
|
231
|
+
if owner.class.name !~ /^HABTM_/ && !@inversed
|
232
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
233
|
+
|
234
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
235
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
236
|
+
else
|
237
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
238
|
+
end
|
212
239
|
end
|
213
240
|
end
|
241
|
+
result
|
214
242
|
end
|
215
|
-
result
|
216
243
|
end
|
217
|
-
|
244
|
+
)
|
218
245
|
|
219
|
-
::ActiveRecord::Associations::HasManyAssociation.prepend(
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
Bullet
|
246
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(
|
247
|
+
Module.new do
|
248
|
+
def empty?
|
249
|
+
result = super
|
250
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
251
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
252
|
+
end
|
253
|
+
result
|
224
254
|
end
|
225
|
-
result
|
226
|
-
end
|
227
255
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
256
|
+
def count_records
|
257
|
+
result = reflection.has_cached_counter?
|
258
|
+
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
|
259
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
260
|
+
end
|
261
|
+
super
|
232
262
|
end
|
233
|
-
super
|
234
|
-
end
|
235
|
-
end)
|
236
|
-
|
237
|
-
::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
|
238
|
-
def writer(record)
|
239
|
-
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
240
|
-
super
|
241
263
|
end
|
242
|
-
|
264
|
+
)
|
243
265
|
end
|
244
266
|
end
|
245
267
|
end
|
data/lib/bullet/bullet_xhr.js
CHANGED
@@ -1,37 +1,42 @@
|
|
1
1
|
(function() {
|
2
2
|
var oldOpen = window.XMLHttpRequest.prototype.open;
|
3
3
|
var oldSend = window.XMLHttpRequest.prototype.send;
|
4
|
-
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Return early if we've already extended prototype. This prevents
|
7
|
+
* "maximum call stack exceeded" errors when used with Turbolinks.
|
8
|
+
* See https://github.com/flyerhzm/bullet/issues/454
|
9
|
+
*/
|
10
|
+
if (isBulletInitiated()) return;
|
11
|
+
|
12
|
+
function isBulletInitiated() {
|
13
|
+
return oldOpen.name == 'bulletXHROpen' && oldSend.name == 'bulletXHRSend';
|
14
|
+
}
|
15
|
+
function bulletXHROpen(_, url) {
|
5
16
|
this._storedUrl = url;
|
6
17
|
return oldOpen.apply(this, arguments);
|
7
18
|
}
|
8
|
-
function
|
19
|
+
function bulletXHRSend() {
|
9
20
|
if (this.onload) {
|
10
21
|
this._storedOnload = this.onload;
|
11
22
|
}
|
12
|
-
this.
|
23
|
+
this.addEventListener('load', bulletXHROnload);
|
13
24
|
return oldSend.apply(this, arguments);
|
14
25
|
}
|
15
|
-
function
|
26
|
+
function bulletXHROnload() {
|
16
27
|
if (
|
17
|
-
this._storedUrl.startsWith(
|
18
|
-
window.location.protocol + '//' + window.location.host,
|
19
|
-
) ||
|
28
|
+
this._storedUrl.startsWith(window.location.protocol + '//' + window.location.host) ||
|
20
29
|
!this._storedUrl.startsWith('http') // For relative paths
|
21
30
|
) {
|
22
31
|
var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
|
23
32
|
if (bulletFooterText) {
|
24
33
|
setTimeout(() => {
|
25
|
-
var oldHtml = document
|
26
|
-
.getElementById('bullet-footer')
|
27
|
-
.innerHTML.split('<br>');
|
34
|
+
var oldHtml = document.getElementById('bullet-footer').innerHTML.split('<br>');
|
28
35
|
var header = oldHtml[0];
|
29
36
|
oldHtml = oldHtml.slice(1, oldHtml.length);
|
30
37
|
var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
|
31
38
|
newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
|
32
|
-
document.getElementById(
|
33
|
-
'bullet-footer',
|
34
|
-
).innerHTML = `${header}<br>${newHtml.join('<br>')}`;
|
39
|
+
document.getElementById('bullet-footer').innerHTML = `${header}<br>${newHtml.join('<br>')}`;
|
35
40
|
}, 0);
|
36
41
|
}
|
37
42
|
var bulletConsoleText = this.getResponseHeader('X-bullet-console-text');
|
@@ -53,6 +58,6 @@
|
|
53
58
|
return this._storedOnload.apply(this, arguments);
|
54
59
|
}
|
55
60
|
}
|
56
|
-
window.XMLHttpRequest.prototype.open =
|
57
|
-
window.XMLHttpRequest.prototype.send =
|
61
|
+
window.XMLHttpRequest.prototype.open = bulletXHROpen;
|
62
|
+
window.XMLHttpRequest.prototype.send = bulletXHRSend;
|
58
63
|
})();
|