bullet 8.0.8 → 8.1.0.beta1

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: ac2296e419c6f32f6ed2e2dbe92a344884d1197b8287bb6c67783a1587c31c72
4
- data.tar.gz: 523f4b66265e7ca8b4d67b19ecd13a547f7ad55acbdbd8190ec1e48a1cb4993e
3
+ metadata.gz: 2574a64c0e7be33a0d8ba57e0c23555e70b09999ae9d6d2d8ce0abf30222c782
4
+ data.tar.gz: 1da3e33d8752367ff389f41f16eff4e7f314084954160a21cc5aac8f1f3b0fca
5
5
  SHA512:
6
- metadata.gz: 463b978de4299a651821f4bf66ce219ba3def30f32d8fbb790086d10b869526578c2f5b441a1c0c5d368c03906e833a86b5ed88cda1a2f983e5769c04bf7efb2
7
- data.tar.gz: 5ab230e714c07790210516fbb760007fbbb49875a4021950b8e5d2c8ab77e132240f632ee50a62ee25dc58f629663f6d9802bee626d870d456284e3d4130ed3e
6
+ metadata.gz: 4b324a99664cfbc4892cee40c2c80f32a46b313c0ea155de6eb36c4809d372e90c8ed5c3eaf20d90de39fbfe03ce1251d128c56f6a30343a2fa25b9d152cff6d
7
+ data.tar.gz: 8be2ed0c9d8ce3e9868c554919f9d5e08b04f16876b1f7999470c05a47ffae82f5c896fd38f39518aef3f8f952b44ffe072f3363bd7e7a24b33bdb53cf51e809
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## Next Release
2
2
 
3
+ ## 8.1.0.beta1 (10/16/2025)
4
+
5
+ * Make `get_relation` private
6
+ * Add support for ActiveRecord 8.1.0.beta1
7
+
3
8
  ## 8.0.8 (05/30/2025)
4
9
 
5
10
  * Add middleware after initializers
data/README.md CHANGED
@@ -71,10 +71,14 @@ config.after_initialize do
71
71
  Bullet.rollbar = true
72
72
  Bullet.add_footer = true
73
73
  Bullet.skip_html_injection = false
74
+ Bullet.skip_http_headers = false
74
75
  Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
75
76
  Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
76
77
  Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
77
78
  Bullet.opentelemetry = true
79
+ Bullet.raise = false
80
+ Bullet.always_append_html_body = false
81
+ Bullet.skip_user_in_notification = false
78
82
  end
79
83
  ```
80
84
 
@@ -82,6 +86,7 @@ The notifier of Bullet is a wrap of [uniform_notifier](https://github.com/flyerh
82
86
 
83
87
  The code above will enable all of the Bullet notification systems:
84
88
  * `Bullet.enable`: enable Bullet gem, otherwise do nothing
89
+ * `Bullet.sentry`: add notifications to sentry
85
90
  * `Bullet.alert`: pop up a JavaScript alert in the browser
86
91
  * `Bullet.bullet_logger`: log to the Bullet log file (Rails.root/log/bullet.log)
87
92
  * `Bullet.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
@@ -89,10 +94,9 @@ The code above will enable all of the Bullet notification systems:
89
94
  * `Bullet.rails_logger`: add warnings directly to the Rails log
90
95
  * `Bullet.honeybadger`: add notifications to Honeybadger
91
96
  * `Bullet.bugsnag`: add notifications to bugsnag
92
- * `Bullet.airbrake`: add notifications to airbrake
93
97
  * `Bullet.appsignal`: add notifications to AppSignal
98
+ * `Bullet.airbrake`: add notifications to airbrake
94
99
  * `Bullet.rollbar`: add notifications to rollbar
95
- * `Bullet.sentry`: add notifications to sentry
96
100
  * `Bullet.add_footer`: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
97
101
  * `Bullet.skip_html_injection`: prevents Bullet from injecting code into the returned HTML. This must be false for receiving alerts, showing the footer or console logging.
98
102
  * `Bullet.skip_http_headers`: don't add headers to API requests, and remove the javascript that relies on them. Note that this prevents bullet from logging warnings to the browser console or updating the footer.
@@ -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
@@ -37,6 +37,8 @@ module Bullet
37
37
  'active_record72'
38
38
  elsif active_record80?
39
39
  'active_record80'
40
+ elsif active_record81?
41
+ 'active_record81'
40
42
  else
41
43
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
42
44
  end
@@ -132,6 +134,10 @@ module Bullet
132
134
  active_record8? && ::ActiveRecord::VERSION::MINOR == 0
133
135
  end
134
136
 
137
+ def active_record81?
138
+ active_record8? && ::ActiveRecord::VERSION::MINOR == 1
139
+ end
140
+
135
141
  def mongoid4x?
136
142
  mongoid? && ::Mongoid::VERSION =~ /\A4/
137
143
  end
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Relations::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, metadata, object, reload = false)
50
52
  result = origin_get_relation(name, metadata, object, reload)
51
53
  Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Relations::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, metadata, object, reload = false)
50
52
  result = origin_get_relation(name, metadata, object, reload)
51
53
  Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Relations::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, metadata, object, reload = false)
50
52
  result = origin_get_relation(name, metadata, object, reload)
51
53
  Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
@@ -61,6 +61,8 @@ module Bullet
61
61
  ::Mongoid::Association::Accessors.class_eval do
62
62
  alias_method :origin_get_relation, :get_relation
63
63
 
64
+ private
65
+
64
66
  def get_relation(name, association, object, reload = false)
65
67
  result = origin_get_relation(name, association, object, reload)
66
68
  Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
@@ -46,6 +46,8 @@ module Bullet
46
46
  ::Mongoid::Association::Accessors.class_eval do
47
47
  alias_method :origin_get_relation, :get_relation
48
48
 
49
+ private
50
+
49
51
  def get_relation(name, association, object, reload = false)
50
52
  result = origin_get_relation(name, association, object, reload)
51
53
  unless association.embedded?
@@ -61,6 +61,8 @@ module Bullet
61
61
  ::Mongoid::Association::Accessors.class_eval do
62
62
  alias_method :origin_get_relation, :get_relation
63
63
 
64
+ private
65
+
64
66
  def get_relation(name, association, object, reload = false)
65
67
  result = origin_get_relation(name, association, object, reload)
66
68
  Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '8.0.8'
4
+ VERSION = '8.1.0.beta1'
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.0.8
4
+ version: 8.1.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
@@ -60,6 +60,7 @@ files:
60
60
  - lib/bullet/active_record71.rb
61
61
  - lib/bullet/active_record72.rb
62
62
  - lib/bullet/active_record80.rb
63
+ - lib/bullet/active_record81.rb
63
64
  - lib/bullet/bullet_xhr.js
64
65
  - lib/bullet/dependency.rb
65
66
  - lib/bullet/detector.rb