bullet 5.7.5 → 6.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 +5 -5
- data/.travis.yml +2 -0
- data/CHANGELOG.md +17 -14
- data/Gemfile.mongoid-7.0 +15 -0
- data/Gemfile.rails-4.0 +1 -1
- data/Gemfile.rails-4.1 +1 -1
- data/Gemfile.rails-4.2 +1 -1
- data/Gemfile.rails-5.0 +1 -1
- data/Gemfile.rails-5.1 +1 -1
- data/Gemfile.rails-5.2 +2 -2
- data/Gemfile.rails-6.0 +15 -0
- data/README.md +8 -5
- data/Rakefile +1 -1
- data/bullet.gemspec +8 -3
- data/lib/bullet/active_record4.rb +5 -13
- data/lib/bullet/active_record41.rb +5 -13
- data/lib/bullet/active_record42.rb +6 -13
- data/lib/bullet/active_record5.rb +15 -13
- data/lib/bullet/active_record52.rb +12 -13
- data/lib/bullet/active_record60.rb +245 -0
- data/lib/bullet/bullet_xhr.js +58 -0
- data/lib/bullet/dependency.rb +17 -5
- data/lib/bullet/detector/association.rb +4 -4
- data/lib/bullet/detector/counter_cache.rb +4 -3
- data/lib/bullet/detector/n_plus_one_query.rb +13 -13
- data/lib/bullet/detector/unused_eager_loading.rb +2 -1
- data/lib/bullet/ext/object.rb +5 -3
- data/lib/bullet/ext/string.rb +1 -1
- data/lib/bullet/mongoid4x.rb +1 -0
- data/lib/bullet/mongoid5x.rb +1 -0
- data/lib/bullet/mongoid6x.rb +5 -4
- data/lib/bullet/mongoid7x.rb +61 -0
- data/lib/bullet/notification/base.rb +1 -1
- data/lib/bullet/rack.rb +45 -27
- data/lib/bullet/stack_trace_filter.rb +43 -25
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +21 -5
- data/lib/generators/bullet/install_generator.rb +3 -3
- data/spec/bullet/detector/n_plus_one_query_spec.rb +23 -0
- data/spec/bullet/ext/object_spec.rb +9 -4
- data/spec/bullet/rack_spec.rb +12 -3
- data/spec/integration/active_record/association_spec.rb +52 -3
- data/spec/integration/counter_cache_spec.rb +1 -1
- data/spec/models/client.rb +2 -0
- data/spec/models/firm.rb +1 -0
- data/spec/models/group.rb +4 -0
- data/spec/models/post.rb +15 -0
- data/spec/support/sqlite_seed.rb +9 -2
- data/test.sh +2 -0
- data/update.sh +1 -0
- metadata +16 -8
|
@@ -0,0 +1,245 @@
|
|
|
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(Module.new do
|
|
17
|
+
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
|
18
|
+
result = super
|
|
19
|
+
if Bullet.start?
|
|
20
|
+
if result.is_a? Array
|
|
21
|
+
if result.size > 1
|
|
22
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
23
|
+
Bullet::Detector::CounterCache.add_possible_objects(result)
|
|
24
|
+
elsif result.size == 1
|
|
25
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
|
|
26
|
+
Bullet::Detector::CounterCache.add_impossible_object(result.first)
|
|
27
|
+
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
|
+
end
|
|
32
|
+
end
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
end)
|
|
36
|
+
|
|
37
|
+
::ActiveRecord::Base.prepend(SaveWithBulletSupport)
|
|
38
|
+
|
|
39
|
+
::ActiveRecord::Relation.prepend(Module.new do
|
|
40
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
41
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
42
|
+
def records
|
|
43
|
+
result = super
|
|
44
|
+
if Bullet.start?
|
|
45
|
+
if result.first.class.name !~ /^HABTM_/
|
|
46
|
+
if result.size > 1
|
|
47
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
48
|
+
Bullet::Detector::CounterCache.add_possible_objects(result)
|
|
49
|
+
elsif result.size == 1
|
|
50
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
|
|
51
|
+
Bullet::Detector::CounterCache.add_impossible_object(result.first)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
end)
|
|
58
|
+
|
|
59
|
+
::ActiveRecord::Associations::Preloader.prepend(Module.new do
|
|
60
|
+
def preloaders_for_one(association, records, scope, polymorphic_parent)
|
|
61
|
+
if Bullet.start?
|
|
62
|
+
records.compact!
|
|
63
|
+
if records.first.class.name !~ /^HABTM_/
|
|
64
|
+
records.each do |record|
|
|
65
|
+
Bullet::Detector::Association.add_object_associations(record, association)
|
|
66
|
+
end
|
|
67
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def preloaders_for_reflection(reflection, records, scope)
|
|
74
|
+
if Bullet.start?
|
|
75
|
+
records.compact!
|
|
76
|
+
if records.first.class.name !~ /^HABTM_/
|
|
77
|
+
records.each do |record|
|
|
78
|
+
Bullet::Detector::Association.add_object_associations(record, reflection.name)
|
|
79
|
+
end
|
|
80
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
super
|
|
84
|
+
end
|
|
85
|
+
end)
|
|
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?
|
|
91
|
+
|
|
92
|
+
records = super
|
|
93
|
+
if Bullet.start?
|
|
94
|
+
associations = (eager_load_values + includes_values).uniq
|
|
95
|
+
records.each do |record|
|
|
96
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
97
|
+
end
|
|
98
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
99
|
+
end
|
|
100
|
+
records
|
|
101
|
+
end
|
|
102
|
+
end)
|
|
103
|
+
|
|
104
|
+
::ActiveRecord::Associations::JoinDependency.prepend(Module.new do
|
|
105
|
+
def instantiate(result_set, &block)
|
|
106
|
+
@bullet_eager_loadings = {}
|
|
107
|
+
records = super
|
|
108
|
+
|
|
109
|
+
if Bullet.start?
|
|
110
|
+
@bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
|
|
111
|
+
objects = eager_loadings_hash.keys
|
|
112
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
records
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def construct(ar_parent, parent, row, seen, model_cache)
|
|
119
|
+
if Bullet.start?
|
|
120
|
+
unless ar_parent.nil?
|
|
121
|
+
parent.children.each do |node|
|
|
122
|
+
key = aliases.column_alias(node, node.primary_key)
|
|
123
|
+
id = row[key]
|
|
124
|
+
next unless id.nil?
|
|
125
|
+
|
|
126
|
+
associations = node.reflection.name
|
|
127
|
+
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
|
128
|
+
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
|
129
|
+
@bullet_eager_loadings[ar_parent.class] ||= {}
|
|
130
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
|
|
131
|
+
@bullet_eager_loadings[ar_parent.class][ar_parent] << associations
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
super
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# call join associations
|
|
140
|
+
def construct_model(record, node, row, model_cache, id)
|
|
141
|
+
result = super
|
|
142
|
+
|
|
143
|
+
if Bullet.start?
|
|
144
|
+
associations = node.reflection.name
|
|
145
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
146
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
147
|
+
@bullet_eager_loadings[record.class] ||= {}
|
|
148
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
149
|
+
@bullet_eager_loadings[record.class][record] << associations
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
result
|
|
153
|
+
end
|
|
154
|
+
end)
|
|
155
|
+
|
|
156
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(Module.new do
|
|
157
|
+
def load_target
|
|
158
|
+
records = super
|
|
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
|
|
167
|
+
|
|
168
|
+
if reflection.through_reflection != through_reflection
|
|
169
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
|
173
|
+
if records.first.class.name !~ /^HABTM_/
|
|
174
|
+
if records.size > 1
|
|
175
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
176
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
177
|
+
elsif records.size == 1
|
|
178
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
179
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
records
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def empty?
|
|
187
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
|
188
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
189
|
+
end
|
|
190
|
+
super
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def include?(object)
|
|
194
|
+
if Bullet.start?
|
|
195
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
196
|
+
end
|
|
197
|
+
super
|
|
198
|
+
end
|
|
199
|
+
end)
|
|
200
|
+
|
|
201
|
+
::ActiveRecord::Associations::SingularAssociation.prepend(Module.new do
|
|
202
|
+
# call has_one and belongs_to associations
|
|
203
|
+
def target
|
|
204
|
+
result = super()
|
|
205
|
+
if Bullet.start?
|
|
206
|
+
if owner.class.name !~ /^HABTM_/ && !@inversed
|
|
207
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
208
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
|
209
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
210
|
+
else
|
|
211
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
result
|
|
216
|
+
end
|
|
217
|
+
end)
|
|
218
|
+
|
|
219
|
+
::ActiveRecord::Associations::HasManyAssociation.prepend(Module.new do
|
|
220
|
+
def empty?
|
|
221
|
+
result = super
|
|
222
|
+
if Bullet.start? && !reflection.has_cached_counter?
|
|
223
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
|
224
|
+
end
|
|
225
|
+
result
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def count_records
|
|
229
|
+
result = reflection.has_cached_counter?
|
|
230
|
+
if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
|
|
231
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
|
232
|
+
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
|
+
end
|
|
242
|
+
end)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var oldOpen = window.XMLHttpRequest.prototype.open;
|
|
3
|
+
var oldSend = window.XMLHttpRequest.prototype.send;
|
|
4
|
+
function newOpen(method, url, async, user, password) {
|
|
5
|
+
this._storedUrl = url;
|
|
6
|
+
return oldOpen.apply(this, arguments);
|
|
7
|
+
}
|
|
8
|
+
function newSend(data) {
|
|
9
|
+
if (this.onload) {
|
|
10
|
+
this._storedOnload = this.onload;
|
|
11
|
+
}
|
|
12
|
+
this.onload = newOnload;
|
|
13
|
+
return oldSend.apply(this, arguments);
|
|
14
|
+
}
|
|
15
|
+
function newOnload() {
|
|
16
|
+
if (
|
|
17
|
+
this._storedUrl.startsWith(
|
|
18
|
+
window.location.protocol + '//' + window.location.host,
|
|
19
|
+
) ||
|
|
20
|
+
!this._storedUrl.startsWith('http') // For relative paths
|
|
21
|
+
) {
|
|
22
|
+
var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
|
|
23
|
+
if (bulletFooterText) {
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
var oldHtml = document
|
|
26
|
+
.getElementById('bullet-footer')
|
|
27
|
+
.innerHTML.split('<br>');
|
|
28
|
+
var header = oldHtml[0];
|
|
29
|
+
oldHtml = oldHtml.slice(1, oldHtml.length);
|
|
30
|
+
var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
|
|
31
|
+
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>')}`;
|
|
35
|
+
}, 0);
|
|
36
|
+
}
|
|
37
|
+
var bulletConsoleText = this.getResponseHeader('X-bullet-console-text');
|
|
38
|
+
if (bulletConsoleText && typeof console !== 'undefined' && console.log) {
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
JSON.parse(bulletConsoleText).forEach(message => {
|
|
41
|
+
if (console.groupCollapsed && console.groupEnd) {
|
|
42
|
+
console.groupCollapsed('Uniform Notifier');
|
|
43
|
+
console.log(message);
|
|
44
|
+
console.groupEnd();
|
|
45
|
+
} else {
|
|
46
|
+
console.log(message);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}, 0);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (this._storedOnload) {
|
|
53
|
+
return this._storedOnload.apply(this, arguments);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
window.XMLHttpRequest.prototype.open = newOpen;
|
|
57
|
+
window.XMLHttpRequest.prototype.send = newSend;
|
|
58
|
+
})();
|
data/lib/bullet/dependency.rb
CHANGED
|
@@ -10,10 +10,6 @@ module Bullet
|
|
|
10
10
|
@active_record ||= defined? ::ActiveRecord
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def rails?
|
|
14
|
-
@rails ||= defined? ::Rails
|
|
15
|
-
end
|
|
16
|
-
|
|
17
13
|
def active_record_version
|
|
18
14
|
@active_record_version ||= begin
|
|
19
15
|
if active_record40?
|
|
@@ -28,8 +24,10 @@ module Bullet
|
|
|
28
24
|
'active_record5'
|
|
29
25
|
elsif active_record52?
|
|
30
26
|
'active_record52'
|
|
27
|
+
elsif active_record60?
|
|
28
|
+
'active_record60'
|
|
31
29
|
else
|
|
32
|
-
raise "Bullet does not support active_record #{::ActiveRecord::VERSION} yet"
|
|
30
|
+
raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
|
|
33
31
|
end
|
|
34
32
|
end
|
|
35
33
|
end
|
|
@@ -42,6 +40,8 @@ module Bullet
|
|
|
42
40
|
'mongoid5x'
|
|
43
41
|
elsif mongoid6x?
|
|
44
42
|
'mongoid6x'
|
|
43
|
+
elsif mongoid7x?
|
|
44
|
+
'mongoid7x'
|
|
45
45
|
else
|
|
46
46
|
raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
|
|
47
47
|
end
|
|
@@ -56,6 +56,10 @@ module Bullet
|
|
|
56
56
|
active_record? && ::ActiveRecord::VERSION::MAJOR == 5
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
+
def active_record6?
|
|
60
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR == 6
|
|
61
|
+
end
|
|
62
|
+
|
|
59
63
|
def active_record40?
|
|
60
64
|
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
|
|
61
65
|
end
|
|
@@ -80,6 +84,10 @@ module Bullet
|
|
|
80
84
|
active_record5? && ::ActiveRecord::VERSION::MINOR == 2
|
|
81
85
|
end
|
|
82
86
|
|
|
87
|
+
def active_record60?
|
|
88
|
+
active_record6? && ::ActiveRecord::VERSION::MINOR == 0
|
|
89
|
+
end
|
|
90
|
+
|
|
83
91
|
def mongoid4x?
|
|
84
92
|
mongoid? && ::Mongoid::VERSION =~ /\A4/
|
|
85
93
|
end
|
|
@@ -91,5 +99,9 @@ module Bullet
|
|
|
91
99
|
def mongoid6x?
|
|
92
100
|
mongoid? && ::Mongoid::VERSION =~ /\A6/
|
|
93
101
|
end
|
|
102
|
+
|
|
103
|
+
def mongoid7x?
|
|
104
|
+
mongoid? && ::Mongoid::VERSION =~ /\A7/
|
|
105
|
+
end
|
|
94
106
|
end
|
|
95
107
|
end
|
|
@@ -7,18 +7,18 @@ module Bullet
|
|
|
7
7
|
def add_object_associations(object, associations)
|
|
8
8
|
return unless Bullet.start?
|
|
9
9
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
10
|
-
return unless object.
|
|
10
|
+
return unless object.bullet_primary_key_value
|
|
11
11
|
|
|
12
|
-
Bullet.debug('Detector::Association#add_object_associations'
|
|
12
|
+
Bullet.debug('Detector::Association#add_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
|
|
13
13
|
object_associations.add(object.bullet_key, associations)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def add_call_object_associations(object, associations)
|
|
17
17
|
return unless Bullet.start?
|
|
18
18
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
19
|
-
return unless object.
|
|
19
|
+
return unless object.bullet_primary_key_value
|
|
20
20
|
|
|
21
|
-
Bullet.debug('Detector::Association#add_call_object_associations'
|
|
21
|
+
Bullet.debug('Detector::Association#add_call_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
|
|
22
22
|
call_object_associations.add(object.bullet_key, associations)
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -7,7 +7,7 @@ module Bullet
|
|
|
7
7
|
def add_counter_cache(object, associations)
|
|
8
8
|
return unless Bullet.start?
|
|
9
9
|
return unless Bullet.counter_cache_enable?
|
|
10
|
-
return unless object.
|
|
10
|
+
return unless object.bullet_primary_key_value
|
|
11
11
|
|
|
12
12
|
Bullet.debug('Detector::CounterCache#add_counter_cache', "object: #{object.bullet_key}, associations: #{associations}")
|
|
13
13
|
if conditions_met?(object, associations)
|
|
@@ -18,8 +18,9 @@ module Bullet
|
|
|
18
18
|
def add_possible_objects(object_or_objects)
|
|
19
19
|
return unless Bullet.start?
|
|
20
20
|
return unless Bullet.counter_cache_enable?
|
|
21
|
+
|
|
21
22
|
objects = Array(object_or_objects)
|
|
22
|
-
return if objects.map(&:
|
|
23
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
|
23
24
|
|
|
24
25
|
Bullet.debug('Detector::CounterCache#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
|
|
25
26
|
objects.each { |object| possible_objects.add object.bullet_key }
|
|
@@ -28,7 +29,7 @@ module Bullet
|
|
|
28
29
|
def add_impossible_object(object)
|
|
29
30
|
return unless Bullet.start?
|
|
30
31
|
return unless Bullet.counter_cache_enable?
|
|
31
|
-
return unless object.
|
|
32
|
+
return unless object.bullet_primary_key_value
|
|
32
33
|
|
|
33
34
|
Bullet.debug('Detector::CounterCache#add_impossible_object', "object: #{object.bullet_key}")
|
|
34
35
|
impossible_objects.add object.bullet_key
|
|
@@ -14,11 +14,12 @@ module Bullet
|
|
|
14
14
|
def call_association(object, associations)
|
|
15
15
|
return unless Bullet.start?
|
|
16
16
|
return unless Bullet.n_plus_one_query_enable?
|
|
17
|
-
return unless object.
|
|
17
|
+
return unless object.bullet_primary_key_value
|
|
18
18
|
return if inversed_objects.include?(object.bullet_key, associations)
|
|
19
|
+
|
|
19
20
|
add_call_object_associations(object, associations)
|
|
20
21
|
|
|
21
|
-
Bullet.debug('Detector::NPlusOneQuery#call_association'
|
|
22
|
+
Bullet.debug('Detector::NPlusOneQuery#call_association', "object: #{object.bullet_key}, associations: #{associations}")
|
|
22
23
|
if !excluded_stacktrace_path? && conditions_met?(object, associations)
|
|
23
24
|
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
|
|
24
25
|
create_notification caller_in_project, object.class.to_s, associations
|
|
@@ -28,28 +29,29 @@ module Bullet
|
|
|
28
29
|
def add_possible_objects(object_or_objects)
|
|
29
30
|
return unless Bullet.start?
|
|
30
31
|
return unless Bullet.n_plus_one_query_enable?
|
|
32
|
+
|
|
31
33
|
objects = Array(object_or_objects)
|
|
32
|
-
return if objects.map(&:
|
|
34
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
|
33
35
|
|
|
34
|
-
Bullet.debug('Detector::NPlusOneQuery#add_possible_objects'
|
|
36
|
+
Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
|
|
35
37
|
objects.each { |object| possible_objects.add object.bullet_key }
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def add_impossible_object(object)
|
|
39
41
|
return unless Bullet.start?
|
|
40
42
|
return unless Bullet.n_plus_one_query_enable?
|
|
41
|
-
return unless object.
|
|
43
|
+
return unless object.bullet_primary_key_value
|
|
42
44
|
|
|
43
|
-
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object'
|
|
45
|
+
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
|
|
44
46
|
impossible_objects.add object.bullet_key
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
def add_inversed_object(object, association)
|
|
48
50
|
return unless Bullet.start?
|
|
49
51
|
return unless Bullet.n_plus_one_query_enable?
|
|
50
|
-
return unless object.
|
|
52
|
+
return unless object.bullet_primary_key_value
|
|
51
53
|
|
|
52
|
-
Bullet.debug('Detector::NPlusOneQuery#add_inversed_object'
|
|
54
|
+
Bullet.debug('Detector::NPlusOneQuery#add_inversed_object', "object: #{object.bullet_key}, association: #{association}")
|
|
53
55
|
inversed_objects.add object.bullet_key, association
|
|
54
56
|
end
|
|
55
57
|
|
|
@@ -69,14 +71,12 @@ module Bullet
|
|
|
69
71
|
# check if object => associations already exists in object_associations.
|
|
70
72
|
def association?(object, associations)
|
|
71
73
|
value = object_associations[object.bullet_key]
|
|
72
|
-
|
|
73
|
-
value.each do |v|
|
|
74
|
+
value&.each do |v|
|
|
74
75
|
# associations == v comparison order is important here because
|
|
75
76
|
# v variable might be a squeel node where :== method is redefined,
|
|
76
77
|
# so it does not compare values at all and return unexpected results
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
end
|
|
78
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
|
79
|
+
return true if result
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
false
|
|
@@ -27,7 +27,7 @@ module Bullet
|
|
|
27
27
|
def add_eager_loadings(objects, associations)
|
|
28
28
|
return unless Bullet.start?
|
|
29
29
|
return unless Bullet.unused_eager_loading_enable?
|
|
30
|
-
return if objects.map(&:
|
|
30
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
|
31
31
|
|
|
32
32
|
Bullet.debug('Detector::UnusedEagerLoading#add_eager_loadings', "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
|
|
33
33
|
bullet_keys = objects.map(&:bullet_key)
|
|
@@ -75,6 +75,7 @@ module Bullet
|
|
|
75
75
|
eager_loadings.similarly_associated(bullet_key, associations).each do |related_bullet_key|
|
|
76
76
|
coa = call_object_associations[related_bullet_key]
|
|
77
77
|
next if coa.nil?
|
|
78
|
+
|
|
78
79
|
all.merge coa
|
|
79
80
|
end
|
|
80
81
|
all.to_a
|
data/lib/bullet/ext/object.rb
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
class Object
|
|
4
4
|
def bullet_key
|
|
5
|
-
"#{self.class}:#{
|
|
5
|
+
"#{self.class}:#{bullet_primary_key_value}"
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def bullet_primary_key_value
|
|
9
|
+
return if respond_to?(:persisted?) && !persisted?
|
|
10
|
+
|
|
9
11
|
if self.class.respond_to?(:primary_keys) && self.class.primary_keys
|
|
10
|
-
self.class.primary_keys.map { |primary_key| send primary_key }.join(','
|
|
12
|
+
self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
|
|
11
13
|
elsif self.class.respond_to?(:primary_key) && self.class.primary_key
|
|
12
14
|
send self.class.primary_key
|
|
13
15
|
else
|
data/lib/bullet/ext/string.rb
CHANGED
data/lib/bullet/mongoid4x.rb
CHANGED
data/lib/bullet/mongoid5x.rb
CHANGED
data/lib/bullet/mongoid6x.rb
CHANGED
|
@@ -10,20 +10,21 @@ module Bullet
|
|
|
10
10
|
alias_method :origin_each, :each
|
|
11
11
|
alias_method :origin_eager_load, :eager_load
|
|
12
12
|
|
|
13
|
-
def first
|
|
14
|
-
result = origin_first
|
|
13
|
+
def first(opt = {})
|
|
14
|
+
result = origin_first(opt)
|
|
15
15
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
16
16
|
result
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def last
|
|
20
|
-
result = origin_last
|
|
19
|
+
def last(opt = {})
|
|
20
|
+
result = origin_last(opt)
|
|
21
21
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
22
22
|
result
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def each(&block)
|
|
26
26
|
return to_enum unless block_given?
|
|
27
|
+
|
|
27
28
|
records = []
|
|
28
29
|
origin_each { |record| records << record }
|
|
29
30
|
if records.length > 1
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bullet
|
|
4
|
+
module Mongoid
|
|
5
|
+
def self.enable
|
|
6
|
+
require 'mongoid'
|
|
7
|
+
::Mongoid::Contextual::Mongo.class_eval do
|
|
8
|
+
alias_method :origin_first, :first
|
|
9
|
+
alias_method :origin_last, :last
|
|
10
|
+
alias_method :origin_each, :each
|
|
11
|
+
alias_method :origin_eager_load, :eager_load
|
|
12
|
+
|
|
13
|
+
def first(opts = {})
|
|
14
|
+
result = origin_first(opts)
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
16
|
+
result
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def last(opts = {})
|
|
20
|
+
result = origin_last(opts)
|
|
21
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def each(&block)
|
|
26
|
+
return to_enum unless block_given?
|
|
27
|
+
|
|
28
|
+
records = []
|
|
29
|
+
origin_each { |record| records << record }
|
|
30
|
+
if records.length > 1
|
|
31
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
32
|
+
elsif records.size == 1
|
|
33
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
34
|
+
end
|
|
35
|
+
records.each(&block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def eager_load(docs)
|
|
39
|
+
associations = criteria.inclusions.map(&:name)
|
|
40
|
+
docs.each do |doc|
|
|
41
|
+
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
|
42
|
+
end
|
|
43
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
|
44
|
+
origin_eager_load(docs)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
::Mongoid::Association::Accessors.class_eval do
|
|
49
|
+
alias_method :origin_get_relation, :get_relation
|
|
50
|
+
|
|
51
|
+
def get_relation(name, association, object, reload = false)
|
|
52
|
+
result = origin_get_relation(name, association, object, reload)
|
|
53
|
+
unless association.embedded?
|
|
54
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
|
55
|
+
end
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|