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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +20 -1
  3. data/CHANGELOG.md +17 -1
  4. data/Gemfile.rails-6.0 +1 -1
  5. data/README.md +23 -9
  6. data/lib/bullet.rb +26 -16
  7. data/lib/bullet/active_job.rb +9 -0
  8. data/lib/bullet/active_record4.rb +9 -24
  9. data/lib/bullet/active_record41.rb +7 -19
  10. data/lib/bullet/active_record42.rb +8 -16
  11. data/lib/bullet/active_record5.rb +188 -170
  12. data/lib/bullet/active_record52.rb +176 -161
  13. data/lib/bullet/active_record60.rb +193 -171
  14. data/lib/bullet/bullet_xhr.js +20 -15
  15. data/lib/bullet/dependency.rb +36 -34
  16. data/lib/bullet/detector/association.rb +24 -18
  17. data/lib/bullet/detector/base.rb +1 -2
  18. data/lib/bullet/detector/counter_cache.rb +10 -6
  19. data/lib/bullet/detector/n_plus_one_query.rb +18 -8
  20. data/lib/bullet/detector/unused_eager_loading.rb +5 -2
  21. data/lib/bullet/mongoid4x.rb +2 -6
  22. data/lib/bullet/mongoid5x.rb +2 -6
  23. data/lib/bullet/mongoid6x.rb +2 -6
  24. data/lib/bullet/mongoid7x.rb +2 -6
  25. data/lib/bullet/notification/base.rb +14 -18
  26. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  27. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  28. data/lib/bullet/rack.rb +5 -3
  29. data/lib/bullet/stack_trace_filter.rb +5 -10
  30. data/lib/bullet/version.rb +1 -1
  31. data/lib/generators/bullet/install_generator.rb +4 -2
  32. data/perf/benchmark.rb +8 -14
  33. data/spec/bullet/detector/counter_cache_spec.rb +5 -5
  34. data/spec/bullet/detector/n_plus_one_query_spec.rb +7 -3
  35. data/spec/bullet/detector/unused_eager_loading_spec.rb +29 -12
  36. data/spec/bullet/notification/base_spec.rb +1 -3
  37. data/spec/bullet/notification/n_plus_one_query_spec.rb +18 -3
  38. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  39. data/spec/bullet/rack_spec.rb +20 -5
  40. data/spec/bullet/registry/association_spec.rb +2 -2
  41. data/spec/bullet/registry/base_spec.rb +1 -1
  42. data/spec/bullet_spec.rb +10 -29
  43. data/spec/integration/active_record/association_spec.rb +42 -122
  44. data/spec/integration/counter_cache_spec.rb +10 -30
  45. data/spec/integration/mongoid/association_spec.rb +18 -32
  46. data/spec/models/folder.rb +1 -2
  47. data/spec/models/group.rb +1 -2
  48. data/spec/models/page.rb +1 -2
  49. data/spec/models/writer.rb +1 -2
  50. data/spec/spec_helper.rb +6 -10
  51. data/spec/support/bullet_ext.rb +8 -9
  52. data/spec/support/mongo_seed.rb +2 -16
  53. 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(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)
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
- end)
37
+ )
36
38
 
37
39
  ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
38
40
 
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)
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
- 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)
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
- 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)
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
- 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?
87
+ )
91
88
 
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)
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
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
100
+ super
99
101
  end
100
- records
101
102
  end
102
- end)
103
+ )
103
104
 
104
- ::ActiveRecord::Associations::JoinDependency.prepend(Module.new do
105
- def instantiate(result_set, &block)
106
- @bullet_eager_loadings = {}
107
- records = super
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
- 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)
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
- 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
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
- super
137
- end
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
- # 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
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
- result
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
- 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
177
+ )
178
+
179
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
180
+ Module.new do
181
+ def load_target
182
+ records = super
167
183
 
168
- if reflection.through_reflection != through_reflection
169
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
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
- 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)
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
- def empty?
187
- if Bullet.start? && !reflection.has_cached_counter?
188
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
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
- def include?(object)
194
- if Bullet.start?
195
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
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
- 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
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
- end)
244
+ )
218
245
 
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)
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
- 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)
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
- end)
264
+ )
243
265
  end
244
266
  end
245
267
  end
@@ -1,37 +1,42 @@
1
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
17
  return oldOpen.apply(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;
23
+ this.addEventListener('load', bulletXHROnload);
13
24
  return oldSend.apply(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
- ) ||
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 = newOpen;
57
- window.XMLHttpRequest.prototype.send = newSend;
61
+ window.XMLHttpRequest.prototype.open = bulletXHROpen;
62
+ window.XMLHttpRequest.prototype.send = bulletXHRSend;
58
63
  })();