bullet 6.1.0 → 7.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +50 -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 +38 -26
  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 +6 -4
  13. data/lib/bullet/active_record52.rb +28 -21
  14. data/lib/bullet/active_record60.rb +27 -20
  15. data/lib/bullet/active_record61.rb +274 -0
  16. data/lib/bullet/active_record70.rb +277 -0
  17. data/lib/bullet/bullet_xhr.js +18 -17
  18. data/lib/bullet/dependency.rb +16 -0
  19. data/lib/bullet/detector/base.rb +2 -1
  20. data/lib/bullet/detector/counter_cache.rb +2 -2
  21. data/lib/bullet/detector/n_plus_one_query.rb +5 -5
  22. data/lib/bullet/detector/unused_eager_loading.rb +2 -2
  23. data/lib/bullet/mongoid4x.rb +1 -1
  24. data/lib/bullet/mongoid5x.rb +1 -1
  25. data/lib/bullet/mongoid6x.rb +1 -1
  26. data/lib/bullet/mongoid7x.rb +32 -17
  27. data/lib/bullet/notification.rb +2 -1
  28. data/lib/bullet/rack.rb +28 -19
  29. data/lib/bullet/stack_trace_filter.rb +7 -9
  30. data/lib/bullet/version.rb +1 -1
  31. data/lib/bullet.rb +29 -28
  32. data/lib/generators/bullet/install_generator.rb +22 -25
  33. data/perf/benchmark.rb +4 -1
  34. data/spec/bullet/detector/counter_cache_spec.rb +1 -1
  35. data/spec/bullet/detector/n_plus_one_query_spec.rb +1 -1
  36. data/spec/bullet/detector/unused_eager_loading_spec.rb +10 -10
  37. data/spec/bullet/ext/object_spec.rb +1 -1
  38. data/spec/bullet/notification/base_spec.rb +4 -4
  39. data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
  40. data/spec/bullet/rack_spec.rb +135 -9
  41. data/spec/bullet_spec.rb +15 -15
  42. data/spec/integration/active_record/association_spec.rb +73 -11
  43. data/spec/integration/counter_cache_spec.rb +4 -4
  44. data/spec/integration/mongoid/association_spec.rb +4 -4
  45. data/spec/models/attachment.rb +5 -0
  46. data/spec/models/deal.rb +5 -0
  47. data/spec/models/folder.rb +2 -1
  48. data/spec/models/group.rb +2 -1
  49. data/spec/models/page.rb +2 -1
  50. data/spec/models/post.rb +2 -0
  51. data/spec/models/role.rb +7 -0
  52. data/spec/models/submission.rb +1 -0
  53. data/spec/models/user.rb +2 -0
  54. data/spec/models/writer.rb +2 -1
  55. data/spec/spec_helper.rb +0 -2
  56. data/spec/support/mongo_seed.rb +1 -0
  57. data/spec/support/sqlite_seed.rb +38 -0
  58. data/test.sh +2 -0
  59. metadata +17 -7
  60. 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
+ end
187
+
188
+ if reflection.through_reflection != through_reflection
189
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
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,277 @@
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
+ end
174
+ )
175
+
176
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
177
+ Module.new do
178
+ def load_target
179
+ records = super
180
+
181
+ if Bullet.start?
182
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
183
+ association = owner.association(reflection.through_reflection.name)
184
+ if association.loaded?
185
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
186
+ Array.wrap(association.target).each do |through_record|
187
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
188
+ end
189
+ end
190
+
191
+ if reflection.through_reflection != through_reflection
192
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
193
+ end
194
+ end
195
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
196
+ if records.first.class.name !~ /^HABTM_/
197
+ if records.size > 1
198
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
199
+ Bullet::Detector::CounterCache.add_possible_objects(records)
200
+ elsif records.size == 1
201
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
202
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
203
+ end
204
+ end
205
+ end
206
+ records
207
+ end
208
+
209
+ def empty?
210
+ if Bullet.start? && !reflection.has_cached_counter?
211
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
212
+ end
213
+ super
214
+ end
215
+
216
+ def include?(object)
217
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
218
+ super
219
+ end
220
+ end
221
+ )
222
+
223
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
224
+ Module.new do
225
+ # call has_one and belongs_to associations
226
+ def reader
227
+ result = super
228
+
229
+ if Bullet.start?
230
+ if owner.class.name !~ /^HABTM_/
231
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
232
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
233
+ association = owner.association(reflection.through_reflection.name)
234
+ Array.wrap(association.target).each do |through_record|
235
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
236
+ end
237
+
238
+ if reflection.through_reflection != through_reflection
239
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
240
+ end
241
+ end
242
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
243
+
244
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
245
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
246
+ else
247
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
248
+ end
249
+ end
250
+ end
251
+ result
252
+ end
253
+ end
254
+ )
255
+
256
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
257
+ Module.new do
258
+ def empty?
259
+ result = super
260
+ if Bullet.start? && !reflection.has_cached_counter?
261
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
262
+ end
263
+ result
264
+ end
265
+
266
+ def count_records
267
+ result = reflection.has_cached_counter?
268
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
269
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
270
+ end
271
+ super
272
+ end
273
+ end
274
+ )
275
+ end
276
+ end
277
+ 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
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Bullet
4
4
  module Detector
5
- class Base; end
5
+ class Base
6
+ end
6
7
  end
7
8
  end
@@ -20,7 +20,7 @@ module Bullet
20
20
  return unless Bullet.start?
21
21
  return unless Bullet.counter_cache_enable?
22
22
 
23
- objects = Array(object_or_objects)
23
+ objects = Array.wrap(object_or_objects)
24
24
  return if objects.map(&:bullet_primary_key_value).compact.empty?
25
25
 
26
26
  Bullet.debug(
@@ -54,7 +54,7 @@ module Bullet
54
54
  private
55
55
 
56
56
  def create_notification(klazz, associations)
57
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
57
+ notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
58
58
 
59
59
  if notify_associations.present?
60
60
  notice = Bullet::Notification::CounterCache.new klazz, notify_associations