bullet 6.1.0 → 7.0.7

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +66 -0
  4. data/Gemfile.rails-6.0 +1 -1
  5. data/Gemfile.rails-6.1 +15 -0
  6. data/Gemfile.rails-7.0 +10 -0
  7. data/MIT-LICENSE +1 -1
  8. data/README.md +41 -27
  9. data/lib/bullet/active_job.rb +5 -1
  10. data/lib/bullet/active_record41.rb +1 -0
  11. data/lib/bullet/active_record42.rb +1 -0
  12. data/lib/bullet/active_record5.rb +10 -8
  13. data/lib/bullet/active_record52.rb +32 -25
  14. data/lib/bullet/active_record60.rb +30 -23
  15. data/lib/bullet/active_record61.rb +274 -0
  16. data/lib/bullet/active_record70.rb +284 -0
  17. data/lib/bullet/bullet_xhr.js +18 -17
  18. data/lib/bullet/dependency.rb +16 -0
  19. data/lib/bullet/detector/association.rb +8 -0
  20. data/lib/bullet/detector/base.rb +2 -1
  21. data/lib/bullet/detector/counter_cache.rb +2 -2
  22. data/lib/bullet/detector/n_plus_one_query.rb +24 -13
  23. data/lib/bullet/detector/unused_eager_loading.rb +3 -3
  24. data/lib/bullet/mongoid4x.rb +1 -1
  25. data/lib/bullet/mongoid5x.rb +1 -1
  26. data/lib/bullet/mongoid6x.rb +1 -1
  27. data/lib/bullet/mongoid7x.rb +32 -17
  28. data/lib/bullet/notification.rb +2 -1
  29. data/lib/bullet/rack.rb +64 -23
  30. data/lib/bullet/registry/call_stack.rb +12 -0
  31. data/lib/bullet/registry.rb +1 -0
  32. data/lib/bullet/stack_trace_filter.rb +15 -15
  33. data/lib/bullet/version.rb +1 -1
  34. data/lib/bullet.rb +35 -29
  35. data/lib/generators/bullet/install_generator.rb +22 -25
  36. data/perf/benchmark.rb +4 -1
  37. data/spec/bullet/detector/counter_cache_spec.rb +1 -1
  38. data/spec/bullet/detector/n_plus_one_query_spec.rb +1 -33
  39. data/spec/bullet/detector/unused_eager_loading_spec.rb +15 -10
  40. data/spec/bullet/ext/object_spec.rb +1 -1
  41. data/spec/bullet/notification/base_spec.rb +4 -4
  42. data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
  43. data/spec/bullet/rack_spec.rb +152 -9
  44. data/spec/bullet/stack_trace_filter_spec.rb +26 -0
  45. data/spec/bullet_spec.rb +15 -15
  46. data/spec/integration/active_record/association_spec.rb +95 -12
  47. data/spec/integration/counter_cache_spec.rb +4 -4
  48. data/spec/integration/mongoid/association_spec.rb +4 -4
  49. data/spec/models/attachment.rb +5 -0
  50. data/spec/models/deal.rb +5 -0
  51. data/spec/models/folder.rb +2 -1
  52. data/spec/models/group.rb +2 -1
  53. data/spec/models/page.rb +2 -1
  54. data/spec/models/post.rb +2 -0
  55. data/spec/models/role.rb +7 -0
  56. data/spec/models/submission.rb +1 -0
  57. data/spec/models/user.rb +2 -0
  58. data/spec/models/writer.rb +2 -1
  59. data/spec/spec_helper.rb +0 -2
  60. data/spec/support/mongo_seed.rb +1 -0
  61. data/spec/support/sqlite_seed.rb +38 -0
  62. data/test.sh +2 -0
  63. metadata +20 -7
  64. data/.travis.yml +0 -33
@@ -0,0 +1,274 @@
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::Associations::JoinDependency.prepend(
106
+ Module.new do
107
+ def instantiate(result_set, strict_loading_value, &block)
108
+ @bullet_eager_loadings = {}
109
+ records = super
110
+
111
+ if Bullet.start?
112
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
113
+ objects = eager_loadings_hash.keys
114
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
115
+ objects,
116
+ eager_loadings_hash[objects.first].to_a
117
+ )
118
+ end
119
+ end
120
+ records
121
+ end
122
+
123
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
124
+ if Bullet.start?
125
+ unless ar_parent.nil?
126
+ parent.children.each do |node|
127
+ key = aliases.column_alias(node, node.primary_key)
128
+ id = row[key]
129
+ next unless id.nil?
130
+
131
+ associations = node.reflection.name
132
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
133
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
134
+ @bullet_eager_loadings[ar_parent.class] ||= {}
135
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
136
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
137
+ end
138
+ end
139
+ end
140
+
141
+ super
142
+ end
143
+
144
+ # call join associations
145
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
146
+ result = super
147
+
148
+ if Bullet.start?
149
+ associations = node.reflection.name
150
+ Bullet::Detector::Association.add_object_associations(record, associations)
151
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
152
+ @bullet_eager_loadings[record.class] ||= {}
153
+ @bullet_eager_loadings[record.class][record] ||= Set.new
154
+ @bullet_eager_loadings[record.class][record] << associations
155
+ end
156
+
157
+ result
158
+ end
159
+ end
160
+ )
161
+
162
+ ::ActiveRecord::Associations::Association.prepend(
163
+ Module.new do
164
+ def inversed_from(record)
165
+ if Bullet.start?
166
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
167
+ end
168
+ super
169
+ end
170
+ end
171
+ )
172
+
173
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
174
+ Module.new do
175
+ def load_target
176
+ records = super
177
+
178
+ if Bullet.start?
179
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
180
+ association = owner.association(reflection.through_reflection.name)
181
+ if association.loaded?
182
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
183
+ Array.wrap(association.target).each do |through_record|
184
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
185
+ end
186
+
187
+ if reflection.through_reflection != through_reflection
188
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
189
+ end
190
+ end
191
+ end
192
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
193
+ if records.first.class.name !~ /^HABTM_/
194
+ if records.size > 1
195
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
196
+ Bullet::Detector::CounterCache.add_possible_objects(records)
197
+ elsif records.size == 1
198
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
199
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
200
+ end
201
+ end
202
+ end
203
+ records
204
+ end
205
+
206
+ def empty?
207
+ if Bullet.start? && !reflection.has_cached_counter?
208
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
209
+ end
210
+ super
211
+ end
212
+
213
+ def include?(object)
214
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
215
+ super
216
+ end
217
+ end
218
+ )
219
+
220
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
221
+ Module.new do
222
+ # call has_one and belongs_to associations
223
+ def target
224
+ result = super()
225
+
226
+ if Bullet.start?
227
+ if owner.class.name !~ /^HABTM_/ && !@inversed
228
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
229
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
230
+ association = owner.association(reflection.through_reflection.name)
231
+ Array.wrap(association.target).each do |through_record|
232
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
233
+ end
234
+
235
+ if reflection.through_reflection != through_reflection
236
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
237
+ end
238
+ end
239
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
240
+
241
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
242
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
243
+ else
244
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
245
+ end
246
+ end
247
+ end
248
+ result
249
+ end
250
+ end
251
+ )
252
+
253
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
254
+ Module.new do
255
+ def empty?
256
+ result = super
257
+ if Bullet.start? && !reflection.has_cached_counter?
258
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
259
+ end
260
+ result
261
+ end
262
+
263
+ def count_records
264
+ result = reflection.has_cached_counter?
265
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
266
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
267
+ end
268
+ super
269
+ end
270
+ end
271
+ )
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,284 @@
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
+
174
+ def inversed_from_queries(record)
175
+ if Bullet.start? && inversable?(record)
176
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
177
+ end
178
+ super
179
+ end
180
+ end
181
+ )
182
+
183
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
184
+ Module.new do
185
+ def load_target
186
+ records = super
187
+
188
+ if Bullet.start?
189
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
190
+ association = owner.association(reflection.through_reflection.name)
191
+ if association.loaded?
192
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
193
+ Array.wrap(association.target).each do |through_record|
194
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
195
+ end
196
+
197
+ if reflection.through_reflection != through_reflection
198
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
199
+ end
200
+ end
201
+ end
202
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
203
+ if records.first.class.name !~ /^HABTM_/
204
+ if records.size > 1
205
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
206
+ Bullet::Detector::CounterCache.add_possible_objects(records)
207
+ elsif records.size == 1
208
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
209
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
210
+ end
211
+ end
212
+ end
213
+ records
214
+ end
215
+
216
+ def empty?
217
+ if Bullet.start? && !reflection.has_cached_counter?
218
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
219
+ end
220
+ super
221
+ end
222
+
223
+ def include?(object)
224
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
225
+ super
226
+ end
227
+ end
228
+ )
229
+
230
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
231
+ Module.new do
232
+ # call has_one and belongs_to associations
233
+ def reader
234
+ result = super
235
+
236
+ if Bullet.start?
237
+ if owner.class.name !~ /^HABTM_/
238
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
239
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
240
+ association = owner.association(reflection.through_reflection.name)
241
+ Array.wrap(association.target).each do |through_record|
242
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
243
+ end
244
+
245
+ if reflection.through_reflection != through_reflection
246
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
247
+ end
248
+ end
249
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
250
+
251
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
252
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
253
+ else
254
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
255
+ end
256
+ end
257
+ end
258
+ result
259
+ end
260
+ end
261
+ )
262
+
263
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
264
+ Module.new do
265
+ def empty?
266
+ result = super
267
+ if Bullet.start? && !reflection.has_cached_counter?
268
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
269
+ end
270
+ result
271
+ end
272
+
273
+ def count_records
274
+ result = reflection.has_cached_counter?
275
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
276
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
277
+ end
278
+ super
279
+ end
280
+ end
281
+ )
282
+ end
283
+ end
284
+ end
@@ -1,4 +1,4 @@
1
- (function() {
1
+ (function () {
2
2
  var oldOpen = window.XMLHttpRequest.prototype.open;
3
3
  var oldSend = window.XMLHttpRequest.prototype.send;
4
4
 
@@ -10,41 +10,42 @@
10
10
  if (isBulletInitiated()) return;
11
11
 
12
12
  function isBulletInitiated() {
13
- return oldOpen.name == 'bulletXHROpen' && oldSend.name == 'bulletXHRSend';
13
+ return oldOpen.name == "bulletXHROpen" && oldSend.name == "bulletXHRSend";
14
14
  }
15
15
  function bulletXHROpen(_, url) {
16
16
  this._storedUrl = url;
17
- return oldOpen.apply(this, arguments);
17
+ return Reflect.apply(oldOpen, this, arguments);
18
18
  }
19
19
  function bulletXHRSend() {
20
20
  if (this.onload) {
21
21
  this._storedOnload = this.onload;
22
22
  }
23
- this.addEventListener('load', bulletXHROnload);
24
- return oldSend.apply(this, arguments);
23
+ this.onload = null;
24
+ this.addEventListener("load", bulletXHROnload);
25
+ return Reflect.apply(oldSend, this, arguments);
25
26
  }
26
27
  function bulletXHROnload() {
27
28
  if (
28
- this._storedUrl.startsWith(window.location.protocol + '//' + window.location.host) ||
29
- !this._storedUrl.startsWith('http') // For relative paths
29
+ this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
30
+ !this._storedUrl.startsWith("http") // For relative paths
30
31
  ) {
31
- var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
32
+ var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
32
33
  if (bulletFooterText) {
33
- setTimeout(() => {
34
- var oldHtml = document.getElementById('bullet-footer').innerHTML.split('<br>');
34
+ setTimeout(function () {
35
+ var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
35
36
  var header = oldHtml[0];
36
37
  oldHtml = oldHtml.slice(1, oldHtml.length);
37
38
  var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
38
39
  newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
39
- document.getElementById('bullet-footer').innerHTML = `${header}<br>${newHtml.join('<br>')}`;
40
+ document.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
40
41
  }, 0);
41
42
  }
42
- var bulletConsoleText = this.getResponseHeader('X-bullet-console-text');
43
- if (bulletConsoleText && typeof console !== 'undefined' && console.log) {
44
- setTimeout(() => {
45
- JSON.parse(bulletConsoleText).forEach(message => {
43
+ var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
44
+ if (bulletConsoleText && typeof console !== "undefined" && console.log) {
45
+ setTimeout(function () {
46
+ JSON.parse(bulletConsoleText).forEach((message) => {
46
47
  if (console.groupCollapsed && console.groupEnd) {
47
- console.groupCollapsed('Uniform Notifier');
48
+ console.groupCollapsed("Uniform Notifier");
48
49
  console.log(message);
49
50
  console.groupEnd();
50
51
  } else {
@@ -55,7 +56,7 @@
55
56
  }
56
57
  }
57
58
  if (this._storedOnload) {
58
- return this._storedOnload.apply(this, arguments);
59
+ return Reflect.apply(this._storedOnload, this, arguments);
59
60
  }
60
61
  }
61
62
  window.XMLHttpRequest.prototype.open = bulletXHROpen;
@@ -27,6 +27,10 @@ module Bullet
27
27
  'active_record52'
28
28
  elsif active_record60?
29
29
  'active_record60'
30
+ elsif active_record61?
31
+ 'active_record61'
32
+ elsif active_record70?
33
+ 'active_record70'
30
34
  else
31
35
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
32
36
  end
@@ -62,6 +66,10 @@ module Bullet
62
66
  active_record? && ::ActiveRecord::VERSION::MAJOR == 6
63
67
  end
64
68
 
69
+ def active_record7?
70
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 7
71
+ end
72
+
65
73
  def active_record40?
66
74
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
67
75
  end
@@ -90,6 +98,14 @@ module Bullet
90
98
  active_record6? && ::ActiveRecord::VERSION::MINOR == 0
91
99
  end
92
100
 
101
+ def active_record61?
102
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 1
103
+ end
104
+
105
+ def active_record70?
106
+ active_record7? && ::ActiveRecord::VERSION::MINOR == 0
107
+ end
108
+
93
109
  def mongoid4x?
94
110
  mongoid? && ::Mongoid::VERSION =~ /\A4/
95
111
  end
@@ -13,6 +13,7 @@ module Bullet
13
13
  'Detector::Association#add_object_associations',
14
14
  "object: #{object.bullet_key}, associations: #{associations}"
15
15
  )
16
+ call_stacks.add(object.bullet_key)
16
17
  object_associations.add(object.bullet_key, associations)
17
18
  end
18
19
 
@@ -25,6 +26,7 @@ module Bullet
25
26
  'Detector::Association#add_call_object_associations',
26
27
  "object: #{object.bullet_key}, associations: #{associations}"
27
28
  )
29
+ call_stacks.add(object.bullet_key)
28
30
  call_object_associations.add(object.bullet_key, associations)
29
31
  end
30
32
 
@@ -76,6 +78,12 @@ module Bullet
76
78
  def eager_loadings
77
79
  Thread.current[:bullet_eager_loadings]
78
80
  end
81
+
82
+ # cal_stacks keeps stacktraces where querie-objects were called from.
83
+ # e.g. { 'Object:111' => [SomeProject/app/controllers/...] }
84
+ def call_stacks
85
+ Thread.current[:bullet_call_stacks]
86
+ end
79
87
  end
80
88
  end
81
89
  end