bullet 7.2.0 → 8.0.8

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0528fba283865b1a94ba6a82d3f5941c63a62e7b34a08332d6208dfde7f28ba
4
- data.tar.gz: 21ad1b7a9c1ab8473ddfff4fcd260346b5088a2f8cf615fbe74abfbe2e70c307
3
+ metadata.gz: ac2296e419c6f32f6ed2e2dbe92a344884d1197b8287bb6c67783a1587c31c72
4
+ data.tar.gz: 523f4b66265e7ca8b4d67b19ecd13a547f7ad55acbdbd8190ec1e48a1cb4993e
5
5
  SHA512:
6
- metadata.gz: 8f6a75db29ab14c89ebd57664f7d96ee5f006fbc816dcbc442678ded0171712aadd360dbb204a567519cab8a49879c067b83a090ea3a8999d212b6801d60fa92
7
- data.tar.gz: 4b51f257fb70205d068e7f233b0e5e30967da936e19481bd9d956b0303f85b67b95792a00a92f272e04333d3861dd69d6ac916c156d06b5db9f5136e0b543dd9
6
+ metadata.gz: 463b978de4299a651821f4bf66ce219ba3def30f32d8fbb790086d10b869526578c2f5b441a1c0c5d368c03906e833a86b5ed88cda1a2f983e5769c04bf7efb2
7
+ data.tar.gz: 5ab230e714c07790210516fbb760007fbbb49875a4021950b8e5d2c8ab77e132240f632ee50a62ee25dc58f629663f6d9802bee626d870d456284e3d4130ed3e
data/CHANGELOG.md CHANGED
@@ -1,8 +1,55 @@
1
1
  ## Next Release
2
2
 
3
+ ## 8.0.8 (05/30/2025)
4
+
5
+ * Add middleware after initializers
6
+ * Fix bullet composite primary key retrieval
7
+
8
+ ## 8.0.7 (05/15/2025)
9
+
10
+ * Try to insert `Bullet::Rack` properly
11
+
12
+ ## 8.0.6 (05/07/2025)
13
+
14
+ * Add CSP nonce for footer styles as well
15
+ * Add support for OpenTelemetry reporting
16
+
17
+ ## 8.0.5 (04/21/2025)
18
+
19
+ * Properly insert ContentSecurityPolicy middleware
20
+ * Properly parse query string
21
+
22
+ ## 8.0.4 (04/18/2025)
23
+
24
+ * Insert bullet middleware before `ContentSecurityPolicy`
25
+ * Support url query `skip_html_injection=true`
26
+ * Mark object as impossible after updating inversed
27
+
28
+ ## 8.0.3 (04/04/2025)
29
+
30
+ * Update non persisted `inversed_objects`
31
+
32
+ ## 8.0.2 (04/02/2025)
33
+
34
+ * Do not cache `bullet_key` if object is not persisted
35
+
36
+ ## 8.0.1 (02/10/2025)
37
+
38
+ * Update benchmark to use sqlite
39
+ * Reduce mem allocations
40
+ * Require active_support's inflections module before requiring the delegation module
41
+
42
+ ## 8.0.0 (11/10/2024)
43
+
44
+ * Support Rails 8
45
+ * Drop Rails 4.0 and 4.1 support
46
+ * Require Ruby at least 2.7.0
47
+ * Store global objects into thread-local variables
48
+ * Avoid globally polluting `::String` and `::Object` classes
49
+
3
50
  ## 7.2.0 (07/12/2024)
4
51
 
5
- * Support rails 7.2
52
+ * Support Rails 7.2
6
53
  * Fix count method signature for active_record5 and active_record60
7
54
 
8
55
  ## 7.1.6 (01/16/2024)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 - 2022 Richard Huang (flyerhzm@gmail.com)
1
+ Copyright (c) 2009 - 2024 Richard Huang (flyerhzm@gmail.com)
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ![Main workflow](https://github.com/flyerhzm/bullet/actions/workflows/main.yml/badge.svg)
4
4
  [![Gem Version](https://badge.fury.io/rb/bullet.svg)](http://badge.fury.io/rb/bullet)
5
5
  [![AwesomeCode Status for flyerhzm/bullet](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/repos/flyerhzm/bullet)
6
- [![Coderwall Endorse](http://api.coderwall.com/flyerhzm/endorsecount.png)](http://coderwall.com/flyerhzm)
6
+ [![Coderwall Endorse](https://coderwall.com/flyerhzm/endorsecount.png)](https://coderwall.com/flyerhzm)
7
7
 
8
8
  The Bullet gem is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries), when you're using eager loading that isn't necessary and when you should use counter cache.
9
9
 
@@ -20,7 +20,7 @@ If you use activerecord 3.x, please use bullet < 5.5.0
20
20
  * [http://railscasts.com/episodes/372-bullet](http://railscasts.com/episodes/372-bullet)
21
21
  * [http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009](http://ruby5.envylabs.com/episodes/9-episode-8-september-8-2009)
22
22
  * [http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1](http://railslab.newrelic.com/2009/10/23/episode-19-on-the-edge-part-1)
23
- * [http://weblog.rubyonrails.org/2009/10/22/community-highlights](http://weblog.rubyonrails.org/2009/10/22/community-highlights)
23
+ * [https://rubyonrails.org/2009/10/22/community-highlights](https://rubyonrails.org/2009/10/22/community-highlights)
24
24
 
25
25
  ## Install
26
26
 
@@ -74,6 +74,7 @@ config.after_initialize do
74
74
  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
75
75
  Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
76
76
  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
77
+ Bullet.opentelemetry = true
77
78
  end
78
79
  ```
79
80
 
@@ -100,6 +101,7 @@ The code above will enable all of the Bullet notification systems:
100
101
  Each item can be a string (match substring), a regex, or an array where the first item is a path to match, and the second
101
102
  item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file.
102
103
  * `Bullet.slack`: add notifications to slack
104
+ * `Bullet.opentelemetry`: add notifications to OpenTelemetry
103
105
  * `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries
104
106
  * `Bullet.always_append_html_body`: always append the html body even if no notifications are present. Note: `console` or `add_footer` must also be true. Useful for Single Page Applications where the initial page load might not have any notifications present.
105
107
  * `Bullet.skip_user_in_notification`: exclude the OS user (`whoami`) from notifications.
@@ -121,8 +123,6 @@ Bullet.unused_eager_loading_enable = false
121
123
  Bullet.counter_cache_enable = false
122
124
  ```
123
125
 
124
- Note: When calling `Bullet.enable`, all other detectors are reset to their defaults (`true`) and need reconfiguring.
125
-
126
126
  ## Safe list
127
127
 
128
128
  Sometimes Bullet may notify you of query problems you don't care to fix, or
@@ -194,6 +194,11 @@ see [https://github.com/flyerhzm/uniform_notifier](https://github.com/flyerhzm/u
194
194
 
195
195
  Growl support is dropped from uniform_notifier 1.16.0, if you still want it, please use uniform_notifier 1.15.0.
196
196
 
197
+ ## URL query control
198
+
199
+ You can add the URL query parameter `skip_html_injection` to make the current HTML request behave as if `Bullet.skip_html_injection` is enabled,
200
+ e.g. `http://localhost:3000/posts?skip_html_injection=true`
201
+
197
202
  ## Important
198
203
 
199
204
  If you find Bullet does not work for you, *please disable your browser's cache*.
@@ -240,7 +245,7 @@ If your application generates a Content-Security-Policy via a separate middlewar
240
245
 
241
246
  ### Run in tests
242
247
 
243
- First you need to enable Bullet in test environment.
248
+ First you need to enable Bullet in the test environment.
244
249
 
245
250
  ```ruby
246
251
  # config/environments/test.rb
@@ -251,11 +256,13 @@ config.after_initialize do
251
256
  end
252
257
  ```
253
258
 
254
- Then wrap each test in Bullet api.
259
+ Then wrap each test in the Bullet api.
260
+
261
+ With RSpec:
255
262
 
256
263
  ```ruby
257
264
  # spec/rails_helper.rb
258
- if Bullet.enable?
265
+ RSpec.configure do |config|
259
266
  config.before(:each) do
260
267
  Bullet.start_request
261
268
  end
@@ -267,6 +274,26 @@ if Bullet.enable?
267
274
  end
268
275
  ```
269
276
 
277
+ With Minitest:
278
+
279
+ ```ruby
280
+ # test/test_helper.rb
281
+ module ActiveSupport
282
+ class TestCase
283
+ def before_setup
284
+ Bullet.start_request
285
+ super
286
+ end
287
+
288
+ def after_teardown
289
+ super
290
+ Bullet.perform_out_of_channel_notifications if Bullet.notification?
291
+ Bullet.end_request
292
+ end
293
+ end
294
+ end
295
+ ```
296
+
270
297
  ## Debug Mode
271
298
 
272
299
  Bullet outputs some details info, to enable debug mode, set
@@ -49,7 +49,10 @@ module Bullet
49
49
 
50
50
  ::ActiveRecord::Persistence.class_eval do
51
51
  def _create_record_with_bullet(*args)
52
- _create_record_without_bullet(*args).tap { Bullet::Detector::NPlusOneQuery.add_impossible_object(self) }
52
+ _create_record_without_bullet(*args).tap do
53
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
54
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
55
+ end
53
56
  end
54
57
  alias_method_chain :_create_record, :bullet
55
58
  end
@@ -52,7 +52,10 @@ module Bullet
52
52
 
53
53
  ::ActiveRecord::Persistence.class_eval do
54
54
  def _create_record_with_bullet(*args)
55
- _create_record_without_bullet(*args).tap { Bullet::Detector::NPlusOneQuery.add_impossible_object(self) }
55
+ _create_record_without_bullet(*args).tap do
56
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
57
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
58
+ end
56
59
  end
57
60
  alias_method_chain :_create_record, :bullet
58
61
  end
@@ -45,7 +45,10 @@ module Bullet
45
45
 
46
46
  ::ActiveRecord::Persistence.class_eval do
47
47
  def _create_record_with_bullet(*args)
48
- _create_record_without_bullet(*args).tap { Bullet::Detector::NPlusOneQuery.add_impossible_object(self) }
48
+ _create_record_without_bullet(*args).tap do
49
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
50
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
51
+ end
49
52
  end
50
53
  alias_method_chain :_create_record, :bullet
51
54
  end
@@ -4,6 +4,7 @@ module Bullet
4
4
  module SaveWithBulletSupport
5
5
  def _create_record(*)
6
6
  super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
7
8
  Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
9
  yield(self) if block_given?
9
10
  end
@@ -4,6 +4,7 @@ module Bullet
4
4
  module SaveWithBulletSupport
5
5
  def _create_record(*)
6
6
  super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
7
8
  Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
9
  yield(self) if block_given?
9
10
  end
@@ -4,6 +4,7 @@ module Bullet
4
4
  module SaveWithBulletSupport
5
5
  def _create_record(*)
6
6
  super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
7
8
  Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
9
  yield(self) if block_given?
9
10
  end
@@ -4,6 +4,7 @@ module Bullet
4
4
  module SaveWithBulletSupport
5
5
  def _create_record(*)
6
6
  super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
7
8
  Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
9
  yield(self) if block_given?
9
10
  end
@@ -4,6 +4,7 @@ module Bullet
4
4
  module SaveWithBulletSupport
5
5
  def _create_record(*)
6
6
  super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
7
8
  Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
9
  yield(self) if block_given?
9
10
  end
@@ -4,6 +4,7 @@ module Bullet
4
4
  module SaveWithBulletSupport
5
5
  def _create_record(*)
6
6
  super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
7
8
  Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
9
  yield(self) if block_given?
9
10
  end
@@ -4,6 +4,7 @@ module Bullet
4
4
  module SaveWithBulletSupport
5
5
  def _create_record(*)
6
6
  super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
7
8
  Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
9
  yield(self) if block_given?
9
10
  end
@@ -0,0 +1,319 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module SaveWithBulletSupport
5
+ def _create_record(*)
6
+ super do
7
+ Bullet::Detector::NPlusOneQuery.update_inversed_object(self)
8
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
9
+ yield(self) if block_given?
10
+ end
11
+ end
12
+ end
13
+
14
+ module ActiveRecord
15
+ def self.enable
16
+ require 'active_record'
17
+ ::ActiveRecord::Base.extend(
18
+ Module.new do
19
+ def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
20
+ result = super
21
+ if Bullet.start?
22
+ if result.is_a? Array
23
+ if result.size > 1
24
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
25
+ Bullet::Detector::CounterCache.add_possible_objects(result)
26
+ elsif result.size == 1
27
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
28
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
29
+ end
30
+ elsif result.is_a? ::ActiveRecord::Base
31
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
32
+ Bullet::Detector::CounterCache.add_impossible_object(result)
33
+ end
34
+ end
35
+ result
36
+ end
37
+ end
38
+ )
39
+
40
+ ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
41
+
42
+ ::ActiveRecord::Relation.prepend(
43
+ Module.new do
44
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
45
+ # if select only one object, then the only one object has impossible to cause N+1 query.
46
+ def records
47
+ result = super
48
+ if Bullet.start?
49
+ if result.first.class.name !~ /^HABTM_/
50
+ if result.size > 1
51
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
52
+ Bullet::Detector::CounterCache.add_possible_objects(result)
53
+ elsif result.size == 1
54
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
55
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
56
+ end
57
+ end
58
+ end
59
+ result
60
+ end
61
+ end
62
+ )
63
+
64
+ ::ActiveRecord::Associations::Preloader::Batch.prepend(
65
+ Module.new do
66
+ def call
67
+ if Bullet.start?
68
+ @preloaders.each do |preloader|
69
+ preloader.records.each { |record|
70
+ Bullet::Detector::Association.add_object_associations(record, preloader.associations)
71
+ }
72
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
73
+ end
74
+ end
75
+ super
76
+ end
77
+ end
78
+ )
79
+
80
+ ::ActiveRecord::Associations::Preloader::Branch.prepend(
81
+ Module.new do
82
+ def preloaders_for_reflection(reflection, reflection_records)
83
+ if Bullet.start?
84
+ reflection_records.compact!
85
+ if reflection_records.first.class.name !~ /^HABTM_/
86
+ reflection_records.each { |record|
87
+ Bullet::Detector::Association.add_object_associations(record, reflection.name)
88
+ }
89
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
90
+ end
91
+ end
92
+ super
93
+ end
94
+ end
95
+ )
96
+
97
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
98
+ Module.new do
99
+ def source_preloaders
100
+ if Bullet.start? && !defined?(@source_preloaders)
101
+ preloaders = super
102
+ preloaders.each do |preloader|
103
+ reflection_name = preloader.send(:reflection).name
104
+ preloader.send(:owners).each do |owner|
105
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
106
+ end
107
+ end
108
+ else
109
+ super
110
+ end
111
+ end
112
+ end
113
+ )
114
+
115
+ ::ActiveRecord::Associations::JoinDependency.prepend(
116
+ Module.new do
117
+ def instantiate(result_set, strict_loading_value, &block)
118
+ @bullet_eager_loadings = {}
119
+ records = super
120
+
121
+ if Bullet.start?
122
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
123
+ objects = eager_loadings_hash.keys
124
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
125
+ objects,
126
+ eager_loadings_hash[objects.first].to_a
127
+ )
128
+ end
129
+ end
130
+ records
131
+ end
132
+
133
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
134
+ if Bullet.start?
135
+ unless ar_parent.nil?
136
+ parent.children.each do |node|
137
+ key = aliases.column_alias(node, node.primary_key)
138
+ id = row[key]
139
+ next unless id.nil?
140
+
141
+ associations = [node.reflection.name]
142
+ if node.reflection.through_reflection?
143
+ associations << node.reflection.through_reflection.name
144
+ end
145
+ associations.each do |association|
146
+ Bullet::Detector::Association.add_object_associations(ar_parent, association)
147
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
148
+ @bullet_eager_loadings[ar_parent.class] ||= {}
149
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
150
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << association
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ super
157
+ end
158
+
159
+ # call join associations
160
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
161
+ result = super
162
+
163
+ if Bullet.start?
164
+ associations = [node.reflection.name]
165
+ if node.reflection.through_reflection?
166
+ associations << node.reflection.through_reflection.name
167
+ end
168
+ associations.each do |association|
169
+ Bullet::Detector::Association.add_object_associations(record, association)
170
+ Bullet::Detector::NPlusOneQuery.call_association(record, association)
171
+ @bullet_eager_loadings[record.class] ||= {}
172
+ @bullet_eager_loadings[record.class][record] ||= Set.new
173
+ @bullet_eager_loadings[record.class][record] << association
174
+ end
175
+ end
176
+
177
+ result
178
+ end
179
+ end
180
+ )
181
+
182
+ ::ActiveRecord::Associations::Association.prepend(
183
+ Module.new do
184
+ def inversed_from(record)
185
+ if Bullet.start?
186
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
187
+ end
188
+ super
189
+ end
190
+
191
+ def inversed_from_queries(record)
192
+ if Bullet.start? && inversable?(record)
193
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
194
+ end
195
+ super
196
+ end
197
+ end
198
+ )
199
+
200
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
201
+ Module.new do
202
+ def load_target
203
+ records = super
204
+
205
+ if Bullet.start?
206
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
207
+ association = owner.association(reflection.through_reflection.name)
208
+ if association.loaded?
209
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
210
+ Array.wrap(association.target).each do |through_record|
211
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
212
+ end
213
+
214
+ if reflection.through_reflection != through_reflection
215
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
216
+ end
217
+ end
218
+ end
219
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
220
+ if records.first.class.name !~ /^HABTM_/
221
+ if records.size > 1
222
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
223
+ Bullet::Detector::CounterCache.add_possible_objects(records)
224
+ elsif records.size == 1
225
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
226
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
227
+ end
228
+ end
229
+ end
230
+ records
231
+ end
232
+
233
+ def empty?
234
+ if Bullet.start? && !reflection.has_cached_counter?
235
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
236
+ end
237
+ super
238
+ end
239
+
240
+ def include?(object)
241
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
242
+ super
243
+ end
244
+ end
245
+ )
246
+
247
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
248
+ Module.new do
249
+ # call has_one and belongs_to associations
250
+ def reader
251
+ result = super
252
+
253
+ if Bullet.start?
254
+ if owner.class.name !~ /^HABTM_/
255
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
256
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
257
+ association = owner.association(reflection.through_reflection.name)
258
+ Array.wrap(association.target).each do |through_record|
259
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
260
+ end
261
+
262
+ if reflection.through_reflection != through_reflection
263
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
264
+ end
265
+ end
266
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
267
+
268
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
269
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
270
+ else
271
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
272
+ end
273
+ end
274
+ end
275
+ result
276
+ end
277
+ end
278
+ )
279
+
280
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
281
+ Module.new do
282
+ def empty?
283
+ result = super
284
+ if Bullet.start? && !reflection.has_cached_counter?
285
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
286
+ end
287
+ result
288
+ end
289
+
290
+ def count_records
291
+ result = reflection.has_cached_counter?
292
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
293
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
294
+ end
295
+ super
296
+ end
297
+ end
298
+ )
299
+
300
+ ::ActiveRecord::Associations::CollectionProxy.prepend(
301
+ Module.new do
302
+ def count(column_name = nil)
303
+ if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
304
+ Bullet::Detector::CounterCache.add_counter_cache(
305
+ proxy_association.owner,
306
+ proxy_association.reflection.name
307
+ )
308
+ Bullet::Detector::NPlusOneQuery.call_association(
309
+ proxy_association.owner,
310
+ proxy_association.reflection.name
311
+ )
312
+ end
313
+ super(column_name)
314
+ end
315
+ end
316
+ )
317
+ end
318
+ end
319
+ end
@@ -35,6 +35,8 @@ module Bullet
35
35
  'active_record71'
36
36
  elsif active_record72?
37
37
  'active_record72'
38
+ elsif active_record80?
39
+ 'active_record80'
38
40
  else
39
41
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
40
42
  end
@@ -54,6 +56,8 @@ module Bullet
54
56
  'mongoid7x'
55
57
  elsif mongoid8x?
56
58
  'mongoid8x'
59
+ elsif mongoid9x?
60
+ 'mongoid9x'
57
61
  else
58
62
  raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
59
63
  end
@@ -76,6 +80,10 @@ module Bullet
76
80
  active_record? && ::ActiveRecord::VERSION::MAJOR == 7
77
81
  end
78
82
 
83
+ def active_record8?
84
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 8
85
+ end
86
+
79
87
  def active_record40?
80
88
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
81
89
  end
@@ -120,6 +128,10 @@ module Bullet
120
128
  active_record7? && ::ActiveRecord::VERSION::MINOR == 2
121
129
  end
122
130
 
131
+ def active_record80?
132
+ active_record8? && ::ActiveRecord::VERSION::MINOR == 0
133
+ end
134
+
123
135
  def mongoid4x?
124
136
  mongoid? && ::Mongoid::VERSION =~ /\A4/
125
137
  end
@@ -139,5 +151,9 @@ module Bullet
139
151
  def mongoid8x?
140
152
  mongoid? && ::Mongoid::VERSION =~ /\A8/
141
153
  end
154
+
155
+ def mongoid9x?
156
+ mongoid? && ::Mongoid::VERSION =~ /\A9/
157
+ end
142
158
  end
143
159
  end