bullet 8.2.0 → 8.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fff8caa10888c5cc1aee062b08ac4c4df6e5911e9b4c592b3e0c89c3faedf089
4
- data.tar.gz: aebe5eb8c7b8624fc2bbe05f42c4c6e8ba5ef8122f519e4e2f984f83d1ac8762
3
+ metadata.gz: ce3dc25a18b75ec4068eae5386c39a96ac90775b2b410efd64f9993790205b83
4
+ data.tar.gz: a777865c06b2b8102a834db3d8a943bd41b2aadf92a91ecbc23f466dcc2bdea6
5
5
  SHA512:
6
- metadata.gz: 6d60f48c25171229b660274b31ae90689f402d0ea1f46fdf2c63467a550765520b46afb70ff683a8beca75da7ddb86629ba5f58e276667a784d37653f3a94688
7
- data.tar.gz: '06973354f3d5158e1ade0f2898577d772b6669d622b176010b6176dbb8b02e5f67c611fbb55668bfee9c93ea873c1183cd28ba30e778a5a6692d0e050a9a3794'
6
+ metadata.gz: 643e6e91d5cb5a3d1de379b738451e82a3e9e7691f2a78a0d7721bac47deaff49181ac93f54cc8fd3390c34732d2bfdea0f4f0701bfb509e0fa9af789f41c519
7
+ data.tar.gz: 70982dc3a2e6cfe721ee126dd138f0c3ff9b0401d5376db35bc88f7245fcb6364f00ebae0e11401658a36e28964d7806d90f6eff15dc498b7e4aecdb32969884
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 8.3.0 (10/10/2026)
4
+
5
+ * Add support for ActiveRecord 8.2
6
+
3
7
  ## 8.2.0 (08/29/2026)
4
8
 
5
9
  * Add thread-safe `Bullet.pause`, `Bullet.resume`, `Bullet.paused?`, and `Bullet.skip` APIs
@@ -0,0 +1,335 @@
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(*args, **kwargs, &block)
20
+ result = super(*args, **kwargs, &block)
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
+
113
+ def through_preloaders
114
+ if Bullet.start? && !defined?(@through_preloaders)
115
+ preloaders = super
116
+ preloaders.each do |preloader|
117
+ reflection_name = preloader.send(:reflection).name
118
+ preloader.send(:owners).each do |owner|
119
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
120
+ end
121
+ end
122
+ else
123
+ super
124
+ end
125
+ end
126
+ end
127
+ )
128
+
129
+ ::ActiveRecord::Associations::JoinDependency.prepend(
130
+ Module.new do
131
+ def instantiate(*args, **kwargs, &block)
132
+ @bullet_eager_loadings = {}
133
+ records = super(*args, **kwargs, &block)
134
+
135
+ if Bullet.start?
136
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
137
+ objects = eager_loadings_hash.keys
138
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
139
+ objects,
140
+ eager_loadings_hash[objects.first].to_a
141
+ )
142
+ end
143
+ end
144
+ records
145
+ end
146
+
147
+ def construct(*args, **kwargs, &block)
148
+ ar_parent, parent, row = args
149
+ if Bullet.start?
150
+ unless ar_parent.nil?
151
+ parent.children.each do |node|
152
+ key = aliases.column_alias(node, node.primary_key)
153
+ id = row[key]
154
+ next unless id.nil?
155
+
156
+ associations = [node.reflection.name]
157
+ if node.reflection.through_reflection?
158
+ associations << node.reflection.through_reflection.name
159
+ end
160
+ associations.each do |association|
161
+ Bullet::Detector::Association.add_object_associations(ar_parent, association)
162
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
163
+ @bullet_eager_loadings[ar_parent.class] ||= {}
164
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
165
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << association
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ super(*args, **kwargs, &block)
172
+ end
173
+
174
+ # call join associations
175
+ def construct_model(*args, **kwargs, &block)
176
+ record, node = args
177
+ result = super(*args, **kwargs, &block)
178
+
179
+ if Bullet.start?
180
+ associations = [node.reflection.name]
181
+ if node.reflection.through_reflection?
182
+ associations << node.reflection.through_reflection.name
183
+ end
184
+ associations.each do |association|
185
+ Bullet::Detector::Association.add_object_associations(record, association)
186
+ Bullet::Detector::NPlusOneQuery.call_association(record, association)
187
+ @bullet_eager_loadings[record.class] ||= {}
188
+ @bullet_eager_loadings[record.class][record] ||= Set.new
189
+ @bullet_eager_loadings[record.class][record] << association
190
+ end
191
+ end
192
+
193
+ result
194
+ end
195
+ end
196
+ )
197
+
198
+ ::ActiveRecord::Associations::Association.prepend(
199
+ Module.new do
200
+ def inversed_from(record)
201
+ if Bullet.start?
202
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
203
+ end
204
+ super
205
+ end
206
+
207
+ def inversed_from_queries(record)
208
+ if Bullet.start? && inversable?(record)
209
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
210
+ end
211
+ super
212
+ end
213
+ end
214
+ )
215
+
216
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
217
+ Module.new do
218
+ def load_target
219
+ records = super
220
+
221
+ if Bullet.start?
222
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
223
+ association = owner.association(reflection.through_reflection.name)
224
+ if association.loaded?
225
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
226
+ Array.wrap(association.target).each do |through_record|
227
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
228
+ end
229
+
230
+ if reflection.through_reflection != through_reflection
231
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
232
+ end
233
+ end
234
+ end
235
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
236
+ if records.first.class.name !~ /^HABTM_/
237
+ if records.size > 1
238
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
239
+ Bullet::Detector::CounterCache.add_possible_objects(records)
240
+ elsif records.size == 1
241
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
242
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
243
+ end
244
+ end
245
+ end
246
+ records
247
+ end
248
+
249
+ def empty?
250
+ if Bullet.start? && !reflection.has_cached_counter?
251
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
252
+ end
253
+ super
254
+ end
255
+
256
+ def include?(object)
257
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations) if Bullet.start?
258
+ super
259
+ end
260
+ end
261
+ )
262
+
263
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
264
+ Module.new do
265
+ # call has_one and belongs_to associations
266
+ def reader
267
+ result = super
268
+
269
+ if Bullet.start?
270
+ if owner.class.name !~ /^HABTM_/
271
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
272
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
273
+ association = owner.association(reflection.through_reflection.name)
274
+ Array.wrap(association.target).each do |through_record|
275
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
276
+ end
277
+
278
+ if reflection.through_reflection != through_reflection
279
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
280
+ end
281
+ end
282
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
283
+
284
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
285
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
286
+ else
287
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
288
+ end
289
+ end
290
+ end
291
+ result
292
+ end
293
+ end
294
+ )
295
+
296
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
297
+ Module.new do
298
+ def empty?
299
+ if Bullet.start? && !reflection.has_cached_counter?
300
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name, caller_locations)
301
+ end
302
+ super
303
+ end
304
+
305
+ def count_records
306
+ result = reflection.has_cached_counter?
307
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
308
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
309
+ end
310
+ super
311
+ end
312
+ end
313
+ )
314
+
315
+ ::ActiveRecord::Associations::CollectionProxy.prepend(
316
+ Module.new do
317
+ def count(column_name = nil)
318
+ if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
319
+ Bullet::Detector::CounterCache.add_counter_cache(
320
+ proxy_association.owner,
321
+ proxy_association.reflection.name
322
+ )
323
+ Bullet::Detector::NPlusOneQuery.call_association(
324
+ proxy_association.owner,
325
+ proxy_association.reflection.name,
326
+ caller_locations
327
+ )
328
+ end
329
+ super(column_name)
330
+ end
331
+ end
332
+ )
333
+ end
334
+ end
335
+ end
@@ -25,6 +25,8 @@ module Bullet
25
25
  'active_record80'
26
26
  elsif active_record81?
27
27
  'active_record81'
28
+ elsif active_record82?
29
+ 'active_record82'
28
30
  else
29
31
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
30
32
  end
@@ -84,6 +86,10 @@ module Bullet
84
86
  active_record8? && ::ActiveRecord::VERSION::MINOR == 1
85
87
  end
86
88
 
89
+ def active_record82?
90
+ active_record8? && ::ActiveRecord::VERSION::MINOR == 2
91
+ end
92
+
87
93
  def mongoid4x?
88
94
  mongoid? && ::Mongoid::VERSION =~ /\A4/
89
95
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '8.2.0'
4
+ VERSION = '8.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.2.0
4
+ version: 8.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
@@ -82,6 +82,7 @@ files:
82
82
  - lib/bullet/active_record72.rb
83
83
  - lib/bullet/active_record80.rb
84
84
  - lib/bullet/active_record81.rb
85
+ - lib/bullet/active_record82.rb
85
86
  - lib/bullet/bullet_xhr.js
86
87
  - lib/bullet/dependency.rb
87
88
  - lib/bullet/detector.rb