bullet 7.2.0 → 8.0.1

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: eb8c8d2264141849784cb8a103b9521a8fad5ba11eba1daa9a5a576f02c2bcc6
4
+ data.tar.gz: e9b5a2a2f123ad3e84007407123103535844dcde3c7e9f3da304eb019d55f96d
5
5
  SHA512:
6
- metadata.gz: 8f6a75db29ab14c89ebd57664f7d96ee5f006fbc816dcbc442678ded0171712aadd360dbb204a567519cab8a49879c067b83a090ea3a8999d212b6801d60fa92
7
- data.tar.gz: 4b51f257fb70205d068e7f233b0e5e30967da936e19481bd9d956b0303f85b67b95792a00a92f272e04333d3861dd69d6ac916c156d06b5db9f5136e0b543dd9
6
+ metadata.gz: bc9c11edab1b705ba7f51f63bf0315c94d5c3f4178684c42da4042adeef52da7570939a91556301dcb076a08a9dc816b89cd38c8a5399c7a8fe81baf2562ca01
7
+ data.tar.gz: aa0cc8a93443e8fa17582ac89dca1eba8714080ebab2d0199b0a298f77ccf3f0a019b457615e18e6dd8827f3573e0fe657d9ada3c36d6e8cd588e2b2c4a4090f
data/CHANGELOG.md CHANGED
@@ -1,8 +1,22 @@
1
1
  ## Next Release
2
2
 
3
+ ## 8.0.1 (02/10/2025)
4
+
5
+ * Update benchmark to use sqlite
6
+ * Reduce mem allocations
7
+ * Require active_support's inflections module before requiring the delegation module
8
+
9
+ ## 8.0.0 (11/10/2024)
10
+
11
+ * Support Rails 8
12
+ * Drop Rails 4.0 and 4.1 support
13
+ * Require Ruby at least 2.7.0
14
+ * Store global objects into thread-local variables
15
+ * Avoid globally polluting `::String` and `::Object` classes
16
+
3
17
  ## 7.2.0 (07/12/2024)
4
18
 
5
- * Support rails 7.2
19
+ * Support Rails 7.2
6
20
  * Fix count method signature for active_record5 and active_record60
7
21
 
8
22
  ## 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
 
@@ -0,0 +1,318 @@
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, allow_retry: false, &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|
69
+ Bullet::Detector::Association.add_object_associations(record, preloader.associations)
70
+ }
71
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
72
+ end
73
+ end
74
+ super
75
+ end
76
+ end
77
+ )
78
+
79
+ ::ActiveRecord::Associations::Preloader::Branch.prepend(
80
+ Module.new do
81
+ def preloaders_for_reflection(reflection, reflection_records)
82
+ if Bullet.start?
83
+ reflection_records.compact!
84
+ if reflection_records.first.class.name !~ /^HABTM_/
85
+ reflection_records.each { |record|
86
+ Bullet::Detector::Association.add_object_associations(record, reflection.name)
87
+ }
88
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
89
+ end
90
+ end
91
+ super
92
+ end
93
+ end
94
+ )
95
+
96
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
97
+ Module.new do
98
+ def source_preloaders
99
+ if Bullet.start? && !defined?(@source_preloaders)
100
+ preloaders = super
101
+ preloaders.each do |preloader|
102
+ reflection_name = preloader.send(:reflection).name
103
+ preloader.send(:owners).each do |owner|
104
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
105
+ end
106
+ end
107
+ else
108
+ super
109
+ end
110
+ end
111
+ end
112
+ )
113
+
114
+ ::ActiveRecord::Associations::JoinDependency.prepend(
115
+ Module.new do
116
+ def instantiate(result_set, strict_loading_value, &block)
117
+ @bullet_eager_loadings = {}
118
+ records = super
119
+
120
+ if Bullet.start?
121
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
122
+ objects = eager_loadings_hash.keys
123
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
124
+ objects,
125
+ eager_loadings_hash[objects.first].to_a
126
+ )
127
+ end
128
+ end
129
+ records
130
+ end
131
+
132
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
133
+ if Bullet.start?
134
+ unless ar_parent.nil?
135
+ parent.children.each do |node|
136
+ key = aliases.column_alias(node, node.primary_key)
137
+ id = row[key]
138
+ next unless id.nil?
139
+
140
+ associations = [node.reflection.name]
141
+ if node.reflection.through_reflection?
142
+ associations << node.reflection.through_reflection.name
143
+ end
144
+ associations.each do |association|
145
+ Bullet::Detector::Association.add_object_associations(ar_parent, association)
146
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, association)
147
+ @bullet_eager_loadings[ar_parent.class] ||= {}
148
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
149
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << association
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ super
156
+ end
157
+
158
+ # call join associations
159
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
160
+ result = super
161
+
162
+ if Bullet.start?
163
+ associations = [node.reflection.name]
164
+ if node.reflection.through_reflection?
165
+ associations << node.reflection.through_reflection.name
166
+ end
167
+ associations.each do |association|
168
+ Bullet::Detector::Association.add_object_associations(record, association)
169
+ Bullet::Detector::NPlusOneQuery.call_association(record, association)
170
+ @bullet_eager_loadings[record.class] ||= {}
171
+ @bullet_eager_loadings[record.class][record] ||= Set.new
172
+ @bullet_eager_loadings[record.class][record] << association
173
+ end
174
+ end
175
+
176
+ result
177
+ end
178
+ end
179
+ )
180
+
181
+ ::ActiveRecord::Associations::Association.prepend(
182
+ Module.new do
183
+ def inversed_from(record)
184
+ if Bullet.start?
185
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
186
+ end
187
+ super
188
+ end
189
+
190
+ def inversed_from_queries(record)
191
+ if Bullet.start? && inversable?(record)
192
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
193
+ end
194
+ super
195
+ end
196
+ end
197
+ )
198
+
199
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
200
+ Module.new do
201
+ def load_target
202
+ records = super
203
+
204
+ if Bullet.start?
205
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
206
+ association = owner.association(reflection.through_reflection.name)
207
+ if association.loaded?
208
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
209
+ Array.wrap(association.target).each do |through_record|
210
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
211
+ end
212
+
213
+ if reflection.through_reflection != through_reflection
214
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
215
+ end
216
+ end
217
+ end
218
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
219
+ if records.first.class.name !~ /^HABTM_/
220
+ if records.size > 1
221
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
222
+ Bullet::Detector::CounterCache.add_possible_objects(records)
223
+ elsif records.size == 1
224
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
225
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
226
+ end
227
+ end
228
+ end
229
+ records
230
+ end
231
+
232
+ def empty?
233
+ if Bullet.start? && !reflection.has_cached_counter?
234
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
235
+ end
236
+ super
237
+ end
238
+
239
+ def include?(object)
240
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
241
+ super
242
+ end
243
+ end
244
+ )
245
+
246
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
247
+ Module.new do
248
+ # call has_one and belongs_to associations
249
+ def reader
250
+ result = super
251
+
252
+ if Bullet.start?
253
+ if owner.class.name !~ /^HABTM_/
254
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
255
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
256
+ association = owner.association(reflection.through_reflection.name)
257
+ Array.wrap(association.target).each do |through_record|
258
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
259
+ end
260
+
261
+ if reflection.through_reflection != through_reflection
262
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
263
+ end
264
+ end
265
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
266
+
267
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
268
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
269
+ else
270
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
271
+ end
272
+ end
273
+ end
274
+ result
275
+ end
276
+ end
277
+ )
278
+
279
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
280
+ Module.new do
281
+ def empty?
282
+ result = super
283
+ if Bullet.start? && !reflection.has_cached_counter?
284
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
285
+ end
286
+ result
287
+ end
288
+
289
+ def count_records
290
+ result = reflection.has_cached_counter?
291
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
292
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
293
+ end
294
+ super
295
+ end
296
+ end
297
+ )
298
+
299
+ ::ActiveRecord::Associations::CollectionProxy.prepend(
300
+ Module.new do
301
+ def count(column_name = nil)
302
+ if Bullet.start? && !proxy_association.is_a?(::ActiveRecord::Associations::ThroughAssociation)
303
+ Bullet::Detector::CounterCache.add_counter_cache(
304
+ proxy_association.owner,
305
+ proxy_association.reflection.name
306
+ )
307
+ Bullet::Detector::NPlusOneQuery.call_association(
308
+ proxy_association.owner,
309
+ proxy_association.reflection.name
310
+ )
311
+ end
312
+ super(column_name)
313
+ end
314
+ end
315
+ )
316
+ end
317
+ end
318
+ 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
@@ -76,6 +78,10 @@ module Bullet
76
78
  active_record? && ::ActiveRecord::VERSION::MAJOR == 7
77
79
  end
78
80
 
81
+ def active_record8?
82
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 8
83
+ end
84
+
79
85
  def active_record40?
80
86
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
81
87
  end
@@ -120,6 +126,10 @@ module Bullet
120
126
  active_record7? && ::ActiveRecord::VERSION::MINOR == 2
121
127
  end
122
128
 
129
+ def active_record80?
130
+ active_record8? && ::ActiveRecord::VERSION::MINOR == 0
131
+ end
132
+
123
133
  def mongoid4x?
124
134
  mongoid? && ::Mongoid::VERSION =~ /\A4/
125
135
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using Bullet::Ext::Object
4
+
3
5
  module Bullet
4
6
  module Detector
5
7
  class Association < Base
@@ -34,7 +36,7 @@ module Bullet
34
36
  # that the objects may cause N+1 query.
35
37
  # e.g. { Post => ["Post:1", "Post:2"] }
36
38
  def possible_objects
37
- Thread.current[:bullet_possible_objects]
39
+ Thread.current.thread_variable_get(:bullet_possible_objects)
38
40
  end
39
41
 
40
42
  # impossible_objects keep the class to objects relationships
@@ -43,7 +45,7 @@ module Bullet
43
45
  # if find collection returns only one object, then the object is impossible object,
44
46
  # impossible_objects are used to avoid treating 1+1 query to N+1 query.
45
47
  def impossible_objects
46
- Thread.current[:bullet_impossible_objects]
48
+ Thread.current.thread_variable_get(:bullet_impossible_objects)
47
49
  end
48
50
 
49
51
  private
@@ -54,7 +56,7 @@ module Bullet
54
56
  # the object_associations keep all associations that may be or may no be
55
57
  # unpreload associations or unused preload associations.
56
58
  def object_associations
57
- Thread.current[:bullet_object_associations]
59
+ Thread.current.thread_variable_get(:bullet_object_associations)
58
60
  end
59
61
 
60
62
  # call_object_associations keep the object relationships
@@ -62,27 +64,27 @@ module Bullet
62
64
  # e.g. { "Post:1" => [:comments] }
63
65
  # they are used to detect unused preload associations.
64
66
  def call_object_associations
65
- Thread.current[:bullet_call_object_associations]
67
+ Thread.current.thread_variable_get(:bullet_call_object_associations)
66
68
  end
67
69
 
68
70
  # inversed_objects keeps object relationships
69
71
  # that association is inversed.
70
72
  # e.g. { "Comment:1" => ["post"] }
71
73
  def inversed_objects
72
- Thread.current[:bullet_inversed_objects]
74
+ Thread.current.thread_variable_get(:bullet_inversed_objects)
73
75
  end
74
76
 
75
77
  # eager_loadings keep the object relationships
76
78
  # that the associations are preloaded by find :include.
77
79
  # e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
78
80
  def eager_loadings
79
- Thread.current[:bullet_eager_loadings]
81
+ Thread.current.thread_variable_get(:bullet_eager_loadings)
80
82
  end
81
83
 
82
- # cal_stacks keeps stacktraces where querie-objects were called from.
84
+ # call_stacks keeps stacktraces where querie-objects were called from.
83
85
  # e.g. { 'Object:111' => [SomeProject/app/controllers/...] }
84
86
  def call_stacks
85
- Thread.current[:bullet_call_stacks]
87
+ Thread.current.thread_variable_get(:bullet_call_stacks)
86
88
  end
87
89
  end
88
90
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using Bullet::Ext::Object
4
+
3
5
  module Bullet
4
6
  module Detector
5
7
  class CounterCache < Base
@@ -44,11 +46,11 @@ module Bullet
44
46
  end
45
47
 
46
48
  def possible_objects
47
- Thread.current[:bullet_counter_possible_objects]
49
+ Thread.current.thread_variable_get(:bullet_counter_possible_objects)
48
50
  end
49
51
 
50
52
  def impossible_objects
51
- Thread.current[:bullet_counter_impossible_objects]
53
+ Thread.current.thread_variable_get(:bullet_counter_impossible_objects)
52
54
  end
53
55
 
54
56
  private
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using Bullet::Ext::Object
4
+
3
5
  module Bullet
4
6
  module Detector
5
7
  class NPlusOneQuery < Association
@@ -25,7 +27,7 @@ module Bullet
25
27
  )
26
28
  if !excluded_stacktrace_path? && conditions_met?(object, associations)
27
29
  Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
28
- create_notification caller_in_project(object.bullet_key), object.class.to_s, associations
30
+ create_notification(caller_in_project(object.bullet_key), object.class.to_s, associations)
29
31
  end
30
32
  end
31
33
 
@@ -36,16 +38,17 @@ module Bullet
36
38
  objects = Array.wrap(object_or_objects)
37
39
  class_names_match_regex = true
38
40
  primary_key_values_are_empty = true
39
- keys_joined = ""
40
- objects.each do |obj|
41
+
42
+ keys_joined = objects.map do |obj|
41
43
  unless obj.class.name =~ /^HABTM_/
42
44
  class_names_match_regex = false
43
45
  end
44
46
  unless obj.bullet_primary_key_value.nil?
45
47
  primary_key_values_are_empty = false
46
48
  end
47
- keys_joined += "#{(keys_joined.empty? ? '' : ', ')}#{obj.bullet_key}"
48
- end
49
+ obj.bullet_key
50
+ end.join(", ")
51
+
49
52
  unless class_names_match_regex || primary_key_values_are_empty
50
53
  Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{keys_joined}")
51
54
  objects.each { |object| possible_objects.add object.bullet_key }
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using Bullet::Ext::Object
4
+ using Bullet::Ext::String
5
+
3
6
  module Bullet
4
7
  module Detector
5
8
  class UnusedEagerLoading < Association
@@ -1,30 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Object
4
- def bullet_key
5
- "#{self.class}:#{bullet_primary_key_value}"
6
- end
3
+ module Bullet
4
+ module Ext
5
+ module Object
6
+ refine ::Object do
7
+ attr_writer :bullet_key, :bullet_primary_key_value
7
8
 
8
- def bullet_primary_key_value
9
- return if respond_to?(:persisted?) && !persisted?
9
+ def bullet_key
10
+ @bullet_key ||= "#{self.class}:#{bullet_primary_key_value}"
11
+ end
10
12
 
11
- if self.class.respond_to?(:primary_keys) && self.class.primary_keys
12
- primary_key = self.class.primary_keys
13
- elsif self.class.respond_to?(:primary_key) && self.class.primary_key
14
- primary_key = self.class.primary_key
15
- else
16
- primary_key = :id
17
- end
13
+ def bullet_primary_key_value
14
+ @bullet_primary_key_value ||= begin
15
+ return if respond_to?(:persisted?) && !persisted?
18
16
 
19
- bullet_join_potential_composite_primary_key(primary_key)
20
- end
17
+ primary_key = self.class.try(:primary_keys) || self.class.try(:primary_key) || :id
21
18
 
22
- private
19
+ bullet_join_potential_composite_primary_key(primary_key)
20
+ end
21
+ end
23
22
 
24
- def bullet_join_potential_composite_primary_key(primary_keys)
25
- return send(primary_keys) unless primary_keys.is_a?(Enumerable)
23
+ private
26
24
 
27
- primary_keys.map { |primary_key| send primary_key }
28
- .join(',')
25
+ def bullet_join_potential_composite_primary_key(primary_keys)
26
+ return send(primary_keys) unless primary_keys.is_a?(Enumerable)
27
+
28
+ primary_keys.map { |primary_key| send primary_key }
29
+ .join(',')
30
+ end
31
+ end
32
+ end
29
33
  end
30
34
  end
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class String
4
- def bullet_class_name
5
- sub(/:[^:]*?$/, '')
3
+ module Bullet
4
+ module Ext
5
+ module String
6
+ refine ::String do
7
+ def bullet_class_name
8
+ last_colon = self.rindex(':')
9
+ last_colon ? self[0...last_colon].dup : self.dup
10
+ end
11
+ end
12
+ end
6
13
  end
7
14
  end
@@ -5,7 +5,7 @@ module Bullet
5
5
  class CallStack < Base
6
6
  # remembers found association backtrace
7
7
  def add(key)
8
- @registry[key] = Thread.current.backtrace
8
+ @registry[key] ||= Thread.current.backtrace
9
9
  end
10
10
  end
11
11
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ using Bullet::Ext::Object
4
+ using Bullet::Ext::String
5
+
3
6
  module Bullet
4
7
  module Registry
5
8
  class Object < Base
@@ -2,10 +2,11 @@
2
2
 
3
3
  require "bundler"
4
4
 
5
+ using Bullet::Ext::Object
6
+
5
7
  module Bullet
6
8
  module StackTraceFilter
7
9
  VENDOR_PATH = '/vendor'
8
- IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
9
10
 
10
11
  # @param bullet_key[String] - use this to get stored call stack from call_stacks object.
11
12
  def caller_in_project(bullet_key = nil)
@@ -54,13 +55,12 @@ module Bullet
54
55
  def location_as_path(location)
55
56
  return location if location.is_a?(String)
56
57
 
57
- IS_RUBY_19 ? location : location.absolute_path.to_s
58
+ location.absolute_path.to_s
58
59
  end
59
60
 
60
61
  def select_caller_locations(bullet_key = nil)
61
- return caller.select { |caller_path| yield caller_path } if IS_RUBY_19
62
-
63
62
  call_stack = bullet_key ? call_stacks[bullet_key] : caller_locations
63
+
64
64
  call_stack.select { |location| yield location }
65
65
  end
66
66
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '7.2.0'
4
+ VERSION = '8.0.1'
5
5
  end
data/lib/bullet.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext/string/inflections'
3
4
  require 'active_support/core_ext/module/delegation'
4
5
  require 'set'
5
6
  require 'uniform_notifier'
@@ -148,42 +149,47 @@ module Bullet
148
149
  end
149
150
 
150
151
  def start_request
151
- Thread.current[:bullet_start] = true
152
- Thread.current[:bullet_notification_collector] = Bullet::NotificationCollector.new
153
-
154
- Thread.current[:bullet_object_associations] = Bullet::Registry::Base.new
155
- Thread.current[:bullet_call_object_associations] = Bullet::Registry::Base.new
156
- Thread.current[:bullet_possible_objects] = Bullet::Registry::Object.new
157
- Thread.current[:bullet_impossible_objects] = Bullet::Registry::Object.new
158
- Thread.current[:bullet_inversed_objects] = Bullet::Registry::Base.new
159
- Thread.current[:bullet_eager_loadings] = Bullet::Registry::Association.new
160
- Thread.current[:bullet_call_stacks] = Bullet::Registry::CallStack.new
152
+ Thread.current.thread_variable_set(:bullet_start, true)
153
+ Thread.current.thread_variable_set(:bullet_notification_collector, Bullet::NotificationCollector.new)
154
+
155
+ Thread.current.thread_variable_set(:bullet_object_associations, Bullet::Registry::Base.new)
156
+ Thread.current.thread_variable_set(:bullet_call_object_associations, Bullet::Registry::Base.new)
157
+ Thread.current.thread_variable_set(:bullet_possible_objects, Bullet::Registry::Object.new)
158
+ Thread.current.thread_variable_set(:bullet_impossible_objects, Bullet::Registry::Object.new)
159
+ Thread.current.thread_variable_set(:bullet_inversed_objects, Bullet::Registry::Base.new)
160
+ Thread.current.thread_variable_set(:bullet_eager_loadings, Bullet::Registry::Association.new)
161
+ Thread.current.thread_variable_set(:bullet_call_stacks, Bullet::Registry::CallStack.new)
162
+
163
+ unless Thread.current.thread_variable_get(:bullet_counter_possible_objects)
164
+ Thread.current.thread_variable_set(:bullet_counter_possible_objects, Bullet::Registry::Object.new)
165
+ end
161
166
 
162
- Thread.current[:bullet_counter_possible_objects] ||= Bullet::Registry::Object.new
163
- Thread.current[:bullet_counter_impossible_objects] ||= Bullet::Registry::Object.new
167
+ unless Thread.current.thread_variable_get(:bullet_counter_impossible_objects)
168
+ Thread.current.thread_variable_set(:bullet_counter_impossible_objects, Bullet::Registry::Object.new)
169
+ end
164
170
  end
165
171
 
166
172
  def end_request
167
- Thread.current[:bullet_start] = nil
168
- Thread.current[:bullet_notification_collector] = nil
173
+ Thread.current.thread_variable_set(:bullet_start, nil)
174
+ Thread.current.thread_variable_set(:bullet_notification_collector, nil)
169
175
 
170
- Thread.current[:bullet_object_associations] = nil
171
- Thread.current[:bullet_call_object_associations] = nil
172
- Thread.current[:bullet_possible_objects] = nil
173
- Thread.current[:bullet_impossible_objects] = nil
174
- Thread.current[:bullet_inversed_objects] = nil
175
- Thread.current[:bullet_eager_loadings] = nil
176
+ Thread.current.thread_variable_set(:bullet_object_associations, nil)
177
+ Thread.current.thread_variable_set(:bullet_call_object_associations, nil)
178
+ Thread.current.thread_variable_set(:bullet_possible_objects, nil)
179
+ Thread.current.thread_variable_set(:bullet_impossible_objects, nil)
180
+ Thread.current.thread_variable_set(:bullet_inversed_objects, nil)
181
+ Thread.current.thread_variable_set(:bullet_eager_loadings, nil)
176
182
 
177
- Thread.current[:bullet_counter_possible_objects] = nil
178
- Thread.current[:bullet_counter_impossible_objects] = nil
183
+ Thread.current.thread_variable_set(:bullet_counter_possible_objects, nil)
184
+ Thread.current.thread_variable_set(:bullet_counter_impossible_objects, nil)
179
185
  end
180
186
 
181
187
  def start?
182
- enable? && Thread.current[:bullet_start]
188
+ enable? && Thread.current.thread_variable_get(:bullet_start)
183
189
  end
184
190
 
185
191
  def notification_collector
186
- Thread.current[:bullet_notification_collector]
192
+ Thread.current.thread_variable_get(:bullet_notification_collector)
187
193
  end
188
194
 
189
195
  def notification?
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.0
4
+ version: 8.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-07-12 00:00:00.000000000 Z
10
+ date: 2025-02-10 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -60,6 +59,7 @@ files:
60
59
  - lib/bullet/active_record70.rb
61
60
  - lib/bullet/active_record71.rb
62
61
  - lib/bullet/active_record72.rb
62
+ - lib/bullet/active_record80.rb
63
63
  - lib/bullet/bullet_xhr.js
64
64
  - lib/bullet/dependency.rb
65
65
  - lib/bullet/detector.rb
@@ -97,7 +97,6 @@ licenses:
97
97
  metadata:
98
98
  changelog_uri: https://github.com/flyerhzm/bullet/blob/main/CHANGELOG.md
99
99
  source_code_uri: https://github.com/flyerhzm/bullet
100
- post_install_message:
101
100
  rdoc_options: []
102
101
  require_paths:
103
102
  - lib
@@ -105,15 +104,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
104
  requirements:
106
105
  - - ">="
107
106
  - !ruby/object:Gem::Version
108
- version: '2.3'
107
+ version: 2.7.0
109
108
  required_rubygems_version: !ruby/object:Gem::Requirement
110
109
  requirements:
111
110
  - - ">="
112
111
  - !ruby/object:Gem::Version
113
112
  version: 1.3.6
114
113
  requirements: []
115
- rubygems_version: 3.5.14
116
- signing_key:
114
+ rubygems_version: 3.6.2
117
115
  specification_version: 4
118
116
  summary: help to kill N+1 queries and unused eager loading.
119
117
  test_files: []