bullet 6.0.0 → 6.1.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.
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