bullet 6.0.0 → 6.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +20 -1
  3. data/CHANGELOG.md +26 -1
  4. data/Gemfile.rails-6.0 +1 -1
  5. data/Gemfile.rails-6.1 +15 -0
  6. data/README.md +30 -9
  7. data/lib/bullet.rb +26 -16
  8. data/lib/bullet/active_job.rb +13 -0
  9. data/lib/bullet/active_record4.rb +9 -24
  10. data/lib/bullet/active_record41.rb +7 -19
  11. data/lib/bullet/active_record42.rb +8 -16
  12. data/lib/bullet/active_record5.rb +188 -170
  13. data/lib/bullet/active_record52.rb +176 -161
  14. data/lib/bullet/active_record60.rb +193 -171
  15. data/lib/bullet/active_record61.rb +267 -0
  16. data/lib/bullet/bullet_xhr.js +32 -27
  17. data/lib/bullet/dependency.rb +42 -34
  18. data/lib/bullet/detector/association.rb +24 -18
  19. data/lib/bullet/detector/base.rb +1 -2
  20. data/lib/bullet/detector/counter_cache.rb +10 -6
  21. data/lib/bullet/detector/n_plus_one_query.rb +18 -8
  22. data/lib/bullet/detector/unused_eager_loading.rb +5 -2
  23. data/lib/bullet/mongoid4x.rb +2 -6
  24. data/lib/bullet/mongoid5x.rb +2 -6
  25. data/lib/bullet/mongoid6x.rb +2 -6
  26. data/lib/bullet/mongoid7x.rb +2 -6
  27. data/lib/bullet/notification/base.rb +14 -18
  28. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  29. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  30. data/lib/bullet/rack.rb +21 -13
  31. data/lib/bullet/stack_trace_filter.rb +5 -10
  32. data/lib/bullet/version.rb +1 -1
  33. data/lib/generators/bullet/install_generator.rb +23 -23
  34. data/perf/benchmark.rb +8 -14
  35. data/spec/bullet/detector/counter_cache_spec.rb +6 -6
  36. data/spec/bullet/detector/n_plus_one_query_spec.rb +7 -3
  37. data/spec/bullet/detector/unused_eager_loading_spec.rb +19 -6
  38. data/spec/bullet/notification/base_spec.rb +1 -3
  39. data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
  40. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  41. data/spec/bullet/rack_spec.rb +76 -5
  42. data/spec/bullet/registry/association_spec.rb +2 -2
  43. data/spec/bullet/registry/base_spec.rb +1 -1
  44. data/spec/bullet_spec.rb +10 -29
  45. data/spec/integration/active_record/association_spec.rb +41 -122
  46. data/spec/integration/counter_cache_spec.rb +10 -30
  47. data/spec/integration/mongoid/association_spec.rb +18 -32
  48. data/spec/models/folder.rb +1 -2
  49. data/spec/models/group.rb +1 -2
  50. data/spec/models/page.rb +1 -2
  51. data/spec/models/writer.rb +1 -2
  52. data/spec/spec_helper.rb +6 -10
  53. data/spec/support/bullet_ext.rb +8 -9
  54. data/spec/support/mongo_seed.rb +2 -16
  55. metadata +9 -6
@@ -0,0 +1,267 @@
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.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)
71
+ end
72
+ end
73
+ super
74
+ end
75
+
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)
82
+ end
83
+ end
84
+ super
85
+ end
86
+ end
87
+ )
88
+
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
99
+ end
100
+ super
101
+ end
102
+ end
103
+ )
104
+
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?
110
+
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)
116
+ end
117
+ records
118
+ end
119
+ end
120
+ )
121
+
122
+ ::ActiveRecord::Associations::JoinDependency.prepend(
123
+ Module.new do
124
+ def instantiate(result_set, strict_loading_value, &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
+ )
135
+ end
136
+ end
137
+ records
138
+ end
139
+
140
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
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?
147
+
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
159
+ end
160
+
161
+ # call join associations
162
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
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
176
+ end
177
+ )
178
+
179
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
180
+ Module.new do
181
+ def load_target
182
+ records = super
183
+
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
195
+ end
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
205
+ end
206
+ end
207
+ records
208
+ end
209
+
210
+ def empty?
211
+ if Bullet.start? && !reflection.has_cached_counter?
212
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
213
+ end
214
+ super
215
+ end
216
+
217
+ def include?(object)
218
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
219
+ super
220
+ end
221
+ end
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
239
+ end
240
+ end
241
+ result
242
+ end
243
+ end
244
+ )
245
+
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
254
+ end
255
+
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
262
+ end
263
+ end
264
+ )
265
+ end
266
+ end
267
+ end
@@ -1,45 +1,50 @@
1
- (function() {
1
+ (function () {
2
2
  var oldOpen = window.XMLHttpRequest.prototype.open;
3
3
  var oldSend = window.XMLHttpRequest.prototype.send;
4
- function newOpen(method, url, async, user, password) {
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
- return oldOpen.apply(this, arguments);
17
+ return Reflect.apply(oldOpen, this, arguments);
7
18
  }
8
- function newSend(data) {
19
+ function bulletXHRSend() {
9
20
  if (this.onload) {
10
21
  this._storedOnload = this.onload;
11
22
  }
12
- this.onload = newOnload;
13
- return oldSend.apply(this, arguments);
23
+ this.addEventListener("load", bulletXHROnload);
24
+ return Reflect.apply(oldSend, this, arguments);
14
25
  }
15
- function newOnload() {
26
+ function bulletXHROnload() {
16
27
  if (
17
- this._storedUrl.startsWith(
18
- window.location.protocol + '//' + window.location.host,
19
- ) ||
20
- !this._storedUrl.startsWith('http') // For relative paths
28
+ this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
29
+ !this._storedUrl.startsWith("http") // For relative paths
21
30
  ) {
22
- var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
31
+ var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
23
32
  if (bulletFooterText) {
24
- setTimeout(() => {
25
- var oldHtml = document
26
- .getElementById('bullet-footer')
27
- .innerHTML.split('<br>');
33
+ setTimeout(function() {
34
+ var oldHtml = document.querySelector("#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.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
35
40
  }, 0);
36
41
  }
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 => {
42
+ var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
43
+ if (bulletConsoleText && typeof console !== "undefined" && console.log) {
44
+ setTimeout(function() {
45
+ JSON.parse(bulletConsoleText).forEach((message) => {
41
46
  if (console.groupCollapsed && console.groupEnd) {
42
- console.groupCollapsed('Uniform Notifier');
47
+ console.groupCollapsed("Uniform Notifier");
43
48
  console.log(message);
44
49
  console.groupEnd();
45
50
  } else {
@@ -50,9 +55,9 @@
50
55
  }
51
56
  }
52
57
  if (this._storedOnload) {
53
- return this._storedOnload.apply(this, arguments);
58
+ return Reflect.apply(this._storedOnload, this, arguments);
54
59
  }
55
60
  }
56
- window.XMLHttpRequest.prototype.open = newOpen;
57
- window.XMLHttpRequest.prototype.send = newSend;
61
+ window.XMLHttpRequest.prototype.open = bulletXHROpen;
62
+ window.XMLHttpRequest.prototype.send = bulletXHRSend;
58
63
  })();
@@ -3,49 +3,53 @@
3
3
  module Bullet
4
4
  module Dependency
5
5
  def mongoid?
6
- @mongoid ||= defined? ::Mongoid
6
+ @mongoid ||= defined?(::Mongoid)
7
7
  end
8
8
 
9
9
  def active_record?
10
- @active_record ||= defined? ::ActiveRecord
10
+ @active_record ||= defined?(::ActiveRecord)
11
11
  end
12
12
 
13
13
  def active_record_version
14
- @active_record_version ||= begin
15
- if active_record40?
16
- 'active_record4'
17
- elsif active_record41?
18
- 'active_record41'
19
- elsif active_record42?
20
- 'active_record42'
21
- elsif active_record50?
22
- 'active_record5'
23
- elsif active_record51?
24
- 'active_record5'
25
- elsif active_record52?
26
- 'active_record52'
27
- elsif active_record60?
28
- 'active_record60'
29
- else
30
- raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
31
- end
32
- end
14
+ @active_record_version ||=
15
+ begin
16
+ if active_record40?
17
+ 'active_record4'
18
+ elsif active_record41?
19
+ 'active_record41'
20
+ elsif active_record42?
21
+ 'active_record42'
22
+ elsif active_record50?
23
+ 'active_record5'
24
+ elsif active_record51?
25
+ 'active_record5'
26
+ elsif active_record52?
27
+ 'active_record52'
28
+ elsif active_record60?
29
+ 'active_record60'
30
+ elsif active_record61?
31
+ 'active_record61'
32
+ else
33
+ raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
34
+ end
35
+ end
33
36
  end
34
37
 
35
38
  def mongoid_version
36
- @mongoid_version ||= begin
37
- if mongoid4x?
38
- 'mongoid4x'
39
- elsif mongoid5x?
40
- 'mongoid5x'
41
- elsif mongoid6x?
42
- 'mongoid6x'
43
- elsif mongoid7x?
44
- 'mongoid7x'
45
- else
46
- raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
47
- end
48
- end
39
+ @mongoid_version ||=
40
+ begin
41
+ if mongoid4x?
42
+ 'mongoid4x'
43
+ elsif mongoid5x?
44
+ 'mongoid5x'
45
+ elsif mongoid6x?
46
+ 'mongoid6x'
47
+ elsif mongoid7x?
48
+ 'mongoid7x'
49
+ else
50
+ raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
51
+ end
52
+ end
49
53
  end
50
54
 
51
55
  def active_record4?
@@ -88,6 +92,10 @@ module Bullet
88
92
  active_record6? && ::ActiveRecord::VERSION::MINOR == 0
89
93
  end
90
94
 
95
+ def active_record61?
96
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 1
97
+ end
98
+
91
99
  def mongoid4x?
92
100
  mongoid? && ::Mongoid::VERSION =~ /\A4/
93
101
  end