bullet 6.1.3 → 7.0.2
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 +27 -0
- data/Gemfile.rails-7.0 +10 -0
- data/README.md +30 -25
- data/lib/bullet/active_record41.rb +1 -0
- data/lib/bullet/active_record42.rb +1 -0
- data/lib/bullet/active_record52.rb +11 -17
- data/lib/bullet/active_record60.rb +11 -17
- data/lib/bullet/active_record61.rb +11 -17
- data/lib/bullet/active_record70.rb +275 -0
- data/lib/bullet/bullet_xhr.js +3 -2
- data/lib/bullet/dependency.rb +10 -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/mongoid7x.rb +26 -9
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +7 -4
- data/lib/bullet/stack_trace_filter.rb +7 -8
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +23 -24
- data/lib/generators/bullet/install_generator.rb +0 -1
- data/perf/benchmark.rb +4 -1
- data/spec/bullet/detector/unused_eager_loading_spec.rb +6 -2
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/notification/base_spec.rb +4 -4
- data/spec/bullet/rack_spec.rb +90 -19
- data/spec/bullet_spec.rb +15 -15
- data/spec/integration/active_record/association_spec.rb +27 -8
- data/spec/integration/counter_cache_spec.rb +4 -4
- data/spec/integration/mongoid/association_spec.rb +1 -1
- 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/role.rb +7 -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 +30 -0
- data/test.sh +2 -0
- metadata +10 -4
- data/.travis.yml +0 -33
@@ -0,0 +1,275 @@
|
|
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::Batch.prepend(
|
64
|
+
Module.new do
|
65
|
+
def call
|
66
|
+
if Bullet.start?
|
67
|
+
@preloaders.each do |preloader|
|
68
|
+
preloader.records.each { |record| Bullet::Detector::Association.add_object_associations(record, preloader.associations) }
|
69
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
)
|
76
|
+
|
77
|
+
::ActiveRecord::Associations::Preloader::Branch.prepend(
|
78
|
+
Module.new do
|
79
|
+
def preloaders_for_reflection(reflection, reflection_records)
|
80
|
+
if Bullet.start?
|
81
|
+
reflection_records.compact!
|
82
|
+
if reflection_records.first.class.name !~ /^HABTM_/
|
83
|
+
reflection_records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
|
84
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
)
|
91
|
+
|
92
|
+
::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
|
93
|
+
Module.new do
|
94
|
+
def preloaded_records
|
95
|
+
if Bullet.start? && !defined?(@preloaded_records)
|
96
|
+
source_preloaders.each do |source_preloader|
|
97
|
+
reflection_name = source_preloader.send(:reflection).name
|
98
|
+
source_preloader.send(:owners).each do |owner|
|
99
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
super
|
104
|
+
end
|
105
|
+
end
|
106
|
+
)
|
107
|
+
|
108
|
+
::ActiveRecord::Associations::JoinDependency.prepend(
|
109
|
+
Module.new do
|
110
|
+
def instantiate(result_set, strict_loading_value, &block)
|
111
|
+
@bullet_eager_loadings = {}
|
112
|
+
records = super
|
113
|
+
|
114
|
+
if Bullet.start?
|
115
|
+
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
|
116
|
+
objects = eager_loadings_hash.keys
|
117
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
|
118
|
+
objects,
|
119
|
+
eager_loadings_hash[objects.first].to_a
|
120
|
+
)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
records
|
124
|
+
end
|
125
|
+
|
126
|
+
def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
|
127
|
+
if Bullet.start?
|
128
|
+
unless ar_parent.nil?
|
129
|
+
parent.children.each do |node|
|
130
|
+
key = aliases.column_alias(node, node.primary_key)
|
131
|
+
id = row[key]
|
132
|
+
next unless id.nil?
|
133
|
+
|
134
|
+
associations = node.reflection.name
|
135
|
+
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
136
|
+
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
137
|
+
@bullet_eager_loadings[ar_parent.class] ||= {}
|
138
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
|
139
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] << associations
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
super
|
145
|
+
end
|
146
|
+
|
147
|
+
# call join associations
|
148
|
+
def construct_model(record, node, row, model_cache, id, strict_loading_value)
|
149
|
+
result = super
|
150
|
+
|
151
|
+
if Bullet.start?
|
152
|
+
associations = node.reflection.name
|
153
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
154
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
155
|
+
@bullet_eager_loadings[record.class] ||= {}
|
156
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
157
|
+
@bullet_eager_loadings[record.class][record] << associations
|
158
|
+
end
|
159
|
+
|
160
|
+
result
|
161
|
+
end
|
162
|
+
end
|
163
|
+
)
|
164
|
+
|
165
|
+
::ActiveRecord::Associations::Association.prepend(
|
166
|
+
Module.new do
|
167
|
+
def inversed_from(record)
|
168
|
+
if Bullet.start?
|
169
|
+
Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
|
170
|
+
end
|
171
|
+
super
|
172
|
+
end
|
173
|
+
end
|
174
|
+
)
|
175
|
+
|
176
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(
|
177
|
+
Module.new do
|
178
|
+
def load_target
|
179
|
+
records = super
|
180
|
+
|
181
|
+
if Bullet.start?
|
182
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
183
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
184
|
+
association = owner.association(reflection.through_reflection.name)
|
185
|
+
Array(association.target).each do |through_record|
|
186
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
187
|
+
end
|
188
|
+
|
189
|
+
if reflection.through_reflection != through_reflection
|
190
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
194
|
+
if records.first.class.name !~ /^HABTM_/
|
195
|
+
if records.size > 1
|
196
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
197
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
198
|
+
elsif records.size == 1
|
199
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
200
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
records
|
205
|
+
end
|
206
|
+
|
207
|
+
def empty?
|
208
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
209
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
210
|
+
end
|
211
|
+
super
|
212
|
+
end
|
213
|
+
|
214
|
+
def include?(object)
|
215
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
|
216
|
+
super
|
217
|
+
end
|
218
|
+
end
|
219
|
+
)
|
220
|
+
|
221
|
+
::ActiveRecord::Associations::SingularAssociation.prepend(
|
222
|
+
Module.new do
|
223
|
+
# call has_one and belongs_to associations
|
224
|
+
def reader
|
225
|
+
result = super
|
226
|
+
|
227
|
+
if Bullet.start?
|
228
|
+
if owner.class.name !~ /^HABTM_/
|
229
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
230
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
231
|
+
association = owner.association(reflection.through_reflection.name)
|
232
|
+
Array(association.target).each do |through_record|
|
233
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
234
|
+
end
|
235
|
+
|
236
|
+
if reflection.through_reflection != through_reflection
|
237
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
241
|
+
|
242
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
243
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
244
|
+
else
|
245
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
result
|
250
|
+
end
|
251
|
+
end
|
252
|
+
)
|
253
|
+
|
254
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(
|
255
|
+
Module.new do
|
256
|
+
def empty?
|
257
|
+
result = super
|
258
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
259
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
260
|
+
end
|
261
|
+
result
|
262
|
+
end
|
263
|
+
|
264
|
+
def count_records
|
265
|
+
result = reflection.has_cached_counter?
|
266
|
+
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
|
267
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
268
|
+
end
|
269
|
+
super
|
270
|
+
end
|
271
|
+
end
|
272
|
+
)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
data/lib/bullet/bullet_xhr.js
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
if (this.onload) {
|
21
21
|
this._storedOnload = this.onload;
|
22
22
|
}
|
23
|
+
this.onload = null;
|
23
24
|
this.addEventListener("load", bulletXHROnload);
|
24
25
|
return Reflect.apply(oldSend, this, arguments);
|
25
26
|
}
|
@@ -30,7 +31,7 @@
|
|
30
31
|
) {
|
31
32
|
var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
|
32
33
|
if (bulletFooterText) {
|
33
|
-
setTimeout(function() {
|
34
|
+
setTimeout(function () {
|
34
35
|
var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
|
35
36
|
var header = oldHtml[0];
|
36
37
|
oldHtml = oldHtml.slice(1, oldHtml.length);
|
@@ -41,7 +42,7 @@
|
|
41
42
|
}
|
42
43
|
var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
|
43
44
|
if (bulletConsoleText && typeof console !== "undefined" && console.log) {
|
44
|
-
setTimeout(function() {
|
45
|
+
setTimeout(function () {
|
45
46
|
JSON.parse(bulletConsoleText).forEach((message) => {
|
46
47
|
if (console.groupCollapsed && console.groupEnd) {
|
47
48
|
console.groupCollapsed("Uniform Notifier");
|
data/lib/bullet/dependency.rb
CHANGED
@@ -29,6 +29,8 @@ module Bullet
|
|
29
29
|
'active_record60'
|
30
30
|
elsif active_record61?
|
31
31
|
'active_record61'
|
32
|
+
elsif active_record70?
|
33
|
+
'active_record70'
|
32
34
|
else
|
33
35
|
raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
|
34
36
|
end
|
@@ -64,6 +66,10 @@ module Bullet
|
|
64
66
|
active_record? && ::ActiveRecord::VERSION::MAJOR == 6
|
65
67
|
end
|
66
68
|
|
69
|
+
def active_record7?
|
70
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR == 7
|
71
|
+
end
|
72
|
+
|
67
73
|
def active_record40?
|
68
74
|
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
|
69
75
|
end
|
@@ -96,6 +102,10 @@ module Bullet
|
|
96
102
|
active_record6? && ::ActiveRecord::VERSION::MINOR == 1
|
97
103
|
end
|
98
104
|
|
105
|
+
def active_record70?
|
106
|
+
active_record7? && ::ActiveRecord::VERSION::MINOR == 0
|
107
|
+
end
|
108
|
+
|
99
109
|
def mongoid4x?
|
100
110
|
mongoid? && ::Mongoid::VERSION =~ /\A4/
|
101
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/mongoid7x.rb
CHANGED
@@ -23,16 +23,33 @@ module Bullet
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
26
|
-
return to_enum unless
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
return to_enum unless block_given?
|
27
|
+
|
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
@@ -22,9 +22,11 @@ module Bullet
|
|
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
|
@@ -40,6 +42,7 @@ module Bullet
|
|
40
42
|
def empty?(response)
|
41
43
|
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
42
44
|
# those should not happen if the status is 200
|
45
|
+
return true if !response.respond_to?(:body) && !response.respond_to?(:first)
|
43
46
|
body = response_body(response)
|
44
47
|
body.nil? || body.empty?
|
45
48
|
end
|
@@ -76,13 +79,13 @@ module Bullet
|
|
76
79
|
end
|
77
80
|
|
78
81
|
def html_request?(headers, response)
|
79
|
-
headers['Content-Type']&.include?('text/html')
|
82
|
+
headers['Content-Type']&.include?('text/html')
|
80
83
|
end
|
81
84
|
|
82
85
|
def response_body(response)
|
83
86
|
if response.respond_to?(:body)
|
84
87
|
Array === response.body ? response.body.first : response.body
|
85
|
-
|
88
|
+
elsif response.respond_to?(:first)
|
86
89
|
response.first
|
87
90
|
end
|
88
91
|
end
|
@@ -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,19 +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')
|
63
|
-
end
|
64
63
|
end
|
65
64
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
@@ -20,9 +20,6 @@ module Bullet
|
|
20
20
|
autoload :Registry, 'bullet/registry'
|
21
21
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
22
22
|
|
23
|
-
BULLET_DEBUG = 'BULLET_DEBUG'
|
24
|
-
TRUE = 'true'
|
25
|
-
|
26
23
|
if defined?(Rails::Railtie)
|
27
24
|
class BulletRailtie < Rails::Railtie
|
28
25
|
initializer 'bullet.configure_rails_initialization' do |app|
|
@@ -38,10 +35,11 @@ module Bullet
|
|
38
35
|
:stacktrace_includes,
|
39
36
|
:stacktrace_excludes,
|
40
37
|
:skip_html_injection
|
41
|
-
attr_reader :
|
42
|
-
attr_accessor :add_footer, :orm_patches_applied
|
38
|
+
attr_reader :safelist
|
39
|
+
attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
|
43
40
|
|
44
|
-
available_notifiers =
|
41
|
+
available_notifiers =
|
42
|
+
UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
|
45
43
|
available_notifiers_options = { to: UniformNotifier }
|
46
44
|
delegate(*available_notifiers, **available_notifiers_options)
|
47
45
|
|
@@ -59,7 +57,7 @@ module Bullet
|
|
59
57
|
@enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
|
60
58
|
|
61
59
|
if enable?
|
62
|
-
|
60
|
+
reset_safelist
|
63
61
|
unless orm_patches_applied
|
64
62
|
self.orm_patches_applied = true
|
65
63
|
Bullet::Mongoid.enable if mongoid?
|
@@ -72,8 +70,9 @@ module Bullet
|
|
72
70
|
!!@enable
|
73
71
|
end
|
74
72
|
|
73
|
+
# Rails.root might be nil if `railties` is a dependency on a project that does not use Rails
|
75
74
|
def app_root
|
76
|
-
(defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
|
75
|
+
@app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s
|
77
76
|
end
|
78
77
|
|
79
78
|
def n_plus_one_query_enable?
|
@@ -96,29 +95,29 @@ module Bullet
|
|
96
95
|
@stacktrace_excludes ||= []
|
97
96
|
end
|
98
97
|
|
99
|
-
def
|
100
|
-
|
101
|
-
@
|
102
|
-
@
|
98
|
+
def add_safelist(options)
|
99
|
+
reset_safelist
|
100
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
101
|
+
@safelist[options[:type]][options[:class_name]] << options[:association].to_sym
|
103
102
|
end
|
104
103
|
|
105
|
-
def
|
106
|
-
|
107
|
-
@
|
108
|
-
@
|
109
|
-
@
|
104
|
+
def delete_safelist(options)
|
105
|
+
reset_safelist
|
106
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
107
|
+
@safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
|
108
|
+
@safelist[options[:type]].delete_if { |_key, val| val.empty? }
|
110
109
|
end
|
111
110
|
|
112
|
-
def
|
113
|
-
Array(@
|
111
|
+
def get_safelist_associations(type, class_name)
|
112
|
+
Array(@safelist[type][class_name])
|
114
113
|
end
|
115
114
|
|
116
|
-
def
|
117
|
-
@
|
115
|
+
def reset_safelist
|
116
|
+
@safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
|
118
117
|
end
|
119
118
|
|
120
|
-
def
|
121
|
-
@
|
119
|
+
def clear_safelist
|
120
|
+
@safelist = nil
|
122
121
|
end
|
123
122
|
|
124
123
|
def bullet_logger=(active)
|
@@ -132,7 +131,7 @@ module Bullet
|
|
132
131
|
end
|
133
132
|
|
134
133
|
def debug(title, message)
|
135
|
-
puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] ==
|
134
|
+
puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
|
136
135
|
end
|
137
136
|
|
138
137
|
def start_request
|
data/perf/benchmark.rb
CHANGED
@@ -30,7 +30,10 @@ end
|
|
30
30
|
|
31
31
|
# create database bullet_benchmark;
|
32
32
|
ActiveRecord::Base.establish_connection(
|
33
|
-
adapter: 'mysql2',
|
33
|
+
adapter: 'mysql2',
|
34
|
+
database: 'bullet_benchmark',
|
35
|
+
server: '/tmp/mysql.socket',
|
36
|
+
username: 'root'
|
34
37
|
)
|
35
38
|
|
36
39
|
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
|
@@ -19,7 +19,9 @@ module Bullet
|
|
19
19
|
it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
|
20
20
|
UnusedEagerLoading.add_eager_loadings([@post], :association)
|
21
21
|
UnusedEagerLoading.add_call_object_associations(@post, :association)
|
22
|
-
expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
22
|
+
expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
23
|
+
[:association]
|
24
|
+
)
|
23
25
|
end
|
24
26
|
|
25
27
|
it 'should not get call associations if not exist in call_object_associations' do
|
@@ -30,7 +32,9 @@ module Bullet
|
|
30
32
|
|
31
33
|
context '.diff_object_associations' do
|
32
34
|
it 'should return associations not exist in call_association' do
|
33
|
-
expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
35
|
+
expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
36
|
+
[:association]
|
37
|
+
)
|
34
38
|
end
|
35
39
|
|
36
40
|
it 'should return empty if associations exist in call_association' do
|