prosopite 1.4.2 → 2.1.2

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: '0509715095d0797998d5bd88ad33bf6390bbc68d5da7f34a10ada120bbcbe96d'
4
- data.tar.gz: 3e05d4ae63f68955c002853d5820bac19ab239b7d4987576bf1d7b9dd85f7d06
3
+ metadata.gz: 0fa7674935ab767fc5eb216492948abd724d07b2a720d345a6da361a51e80efe
4
+ data.tar.gz: 23b72eae523301b0b95d244577a019a266420fb4dc1560751b965ad6c57ea517
5
5
  SHA512:
6
- metadata.gz: 63408215134ec404aba9cd4728ae2bdbb57b0924c08449a1cccc1f72d46cfdedec33685643b452eac0a23ff05abbb0fc74f96954b0ec874b94aef974f46d2ed0
7
- data.tar.gz: d1b9a6d908bf2b17bbe9710b446a0c7b3f66a1a5b0468aaabb37b085d152d609a973faec5ae529d859ecea8c297d93ff3128917344ae6ff0159fb4fc5041a10b
6
+ metadata.gz: 804e8468b23f1a6e3911a28932fb643d67aa2a5a9dc0d55662307b9ce8f412ff60c5ed3a0aded37bfa17f5dd6634f121132758f3997555147495f5ceed2a9beb
7
+ data.tar.gz: 120685a04d3a23e45b5cb6b14a8b544e40aeec4329e750156df91b3c42f4b51ce71ab1a26d28f9fb6334656a694337a58da51fa848ee46ea8341c2135e9c0b73
@@ -4,7 +4,7 @@ jobs:
4
4
  test:
5
5
  strategy:
6
6
  matrix:
7
- ruby: [2.7, '3.0', 3.1, 3.2]
7
+ ruby: [3.2, 3.3, 3.4]
8
8
  runs-on: ubuntu-latest
9
9
  steps:
10
10
  - uses: actions/checkout@v3
data/Gemfile.lock CHANGED
@@ -1,87 +1,136 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prosopite (1.4.2)
4
+ prosopite (2.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- actionpack (6.1.3)
10
- actionview (= 6.1.3)
11
- activesupport (= 6.1.3)
12
- rack (~> 2.0, >= 2.0.9)
9
+ actionpack (8.0.1)
10
+ actionview (= 8.0.1)
11
+ activesupport (= 8.0.1)
12
+ nokogiri (>= 1.8.5)
13
+ rack (>= 2.2.4)
14
+ rack-session (>= 1.0.1)
13
15
  rack-test (>= 0.6.3)
14
- rails-dom-testing (~> 2.0)
15
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
16
- actionview (6.1.3)
17
- activesupport (= 6.1.3)
16
+ rails-dom-testing (~> 2.2)
17
+ rails-html-sanitizer (~> 1.6)
18
+ useragent (~> 0.16)
19
+ actionview (8.0.1)
20
+ activesupport (= 8.0.1)
18
21
  builder (~> 3.1)
19
- erubi (~> 1.4)
20
- rails-dom-testing (~> 2.0)
21
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
22
- activemodel (6.1.3)
23
- activesupport (= 6.1.3)
24
- activerecord (6.1.3)
25
- activemodel (= 6.1.3)
26
- activesupport (= 6.1.3)
27
- activesupport (6.1.3)
28
- concurrent-ruby (~> 1.0, >= 1.0.2)
22
+ erubi (~> 1.11)
23
+ rails-dom-testing (~> 2.2)
24
+ rails-html-sanitizer (~> 1.6)
25
+ activemodel (8.0.1)
26
+ activesupport (= 8.0.1)
27
+ activerecord (8.0.1)
28
+ activemodel (= 8.0.1)
29
+ activesupport (= 8.0.1)
30
+ timeout (>= 0.4.0)
31
+ activesupport (8.0.1)
32
+ base64
33
+ benchmark (>= 0.3)
34
+ bigdecimal
35
+ concurrent-ruby (~> 1.0, >= 1.3.1)
36
+ connection_pool (>= 2.2.5)
37
+ drb
29
38
  i18n (>= 1.6, < 2)
39
+ logger (>= 1.4.2)
30
40
  minitest (>= 5.1)
31
- tzinfo (~> 2.0)
32
- zeitwerk (~> 2.3)
41
+ securerandom (>= 0.3)
42
+ tzinfo (~> 2.0, >= 2.0.5)
43
+ uri (>= 0.13.1)
33
44
  ansi (1.5.0)
45
+ base64 (0.2.0)
46
+ benchmark (0.4.0)
47
+ bigdecimal (3.1.9)
34
48
  builder (3.2.4)
35
49
  coderay (1.1.3)
36
- concurrent-ruby (1.1.8)
50
+ concurrent-ruby (1.3.5)
51
+ connection_pool (2.5.0)
37
52
  crass (1.0.6)
53
+ date (3.4.1)
54
+ drb (2.2.1)
38
55
  erubi (1.12.0)
39
- factory_bot (6.1.0)
40
- activesupport (>= 5.0.0)
56
+ factory_bot (6.5.1)
57
+ activesupport (>= 6.1.0)
41
58
  i18n (1.8.9)
42
59
  concurrent-ruby (~> 1.0)
43
- loofah (2.19.1)
60
+ io-console (0.8.0)
61
+ irb (1.15.1)
62
+ pp (>= 0.6.0)
63
+ rdoc (>= 4.0.0)
64
+ reline (>= 0.4.2)
65
+ logger (1.6.6)
66
+ loofah (2.24.0)
44
67
  crass (~> 1.0.2)
45
- nokogiri (>= 1.5.9)
68
+ nokogiri (>= 1.12.0)
46
69
  method_source (1.0.0)
47
- mini_portile2 (2.8.1)
48
- minitest (5.14.3)
70
+ mini_portile2 (2.8.8)
71
+ minitest (5.25.4)
49
72
  minitest-reporters (1.5.0)
50
73
  ansi
51
74
  builder
52
75
  minitest (>= 5.0)
53
76
  ruby-progressbar
54
- nokogiri (1.14.1)
55
- mini_portile2 (~> 2.8.0)
77
+ nokogiri (1.18.3)
78
+ mini_portile2 (~> 2.8.2)
56
79
  racc (~> 1.4)
57
- nokogiri (1.14.1-x86_64-linux)
80
+ nokogiri (1.18.3-x86_64-linux-gnu)
58
81
  racc (~> 1.4)
59
82
  pg_query (1.3.0)
83
+ pp (0.6.2)
84
+ prettyprint
85
+ prettyprint (0.2.0)
60
86
  pry (0.14.0)
61
87
  coderay (~> 1.1)
62
88
  method_source (~> 1.0)
63
- racc (1.6.2)
64
- rack (2.2.6.2)
89
+ psych (5.2.3)
90
+ date
91
+ stringio
92
+ racc (1.8.1)
93
+ rack (3.1.10)
94
+ rack-session (2.1.0)
95
+ base64 (>= 0.1.0)
96
+ rack (>= 3.0.0)
65
97
  rack-test (2.0.2)
66
98
  rack (>= 1.3)
67
- rails-dom-testing (2.0.3)
68
- activesupport (>= 4.2.0)
99
+ rackup (2.2.1)
100
+ rack (>= 3)
101
+ rails-dom-testing (2.2.0)
102
+ activesupport (>= 5.0.0)
103
+ minitest
69
104
  nokogiri (>= 1.6)
70
- rails-html-sanitizer (1.5.0)
71
- loofah (~> 2.19, >= 2.19.1)
72
- railties (6.1.3)
73
- actionpack (= 6.1.3)
74
- activesupport (= 6.1.3)
75
- method_source
76
- rake (>= 0.8.7)
77
- thor (~> 1.0)
105
+ rails-html-sanitizer (1.6.2)
106
+ loofah (~> 2.21)
107
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
108
+ railties (8.0.1)
109
+ actionpack (= 8.0.1)
110
+ activesupport (= 8.0.1)
111
+ irb (~> 1.13)
112
+ rackup (>= 1.0.0)
113
+ rake (>= 12.2)
114
+ thor (~> 1.0, >= 1.2.2)
115
+ zeitwerk (~> 2.6)
78
116
  rake (13.0.6)
117
+ rdoc (6.12.0)
118
+ psych (>= 4.0.0)
119
+ reline (0.6.0)
120
+ io-console (~> 0.5)
79
121
  ruby-progressbar (1.11.0)
80
- sqlite3 (1.4.2)
81
- thor (1.2.1)
82
- tzinfo (2.0.4)
122
+ securerandom (0.4.1)
123
+ sqlite3 (2.5.0)
124
+ mini_portile2 (~> 2.8.0)
125
+ sqlite3 (2.5.0-x86_64-linux-gnu)
126
+ stringio (3.1.4)
127
+ thor (1.3.2)
128
+ timeout (0.4.3)
129
+ tzinfo (2.0.6)
83
130
  concurrent-ruby (~> 1.0)
84
- zeitwerk (2.4.2)
131
+ uri (1.0.2)
132
+ useragent (0.16.11)
133
+ zeitwerk (2.7.2)
85
134
 
86
135
  PLATFORMS
87
136
  ruby
@@ -100,4 +149,4 @@ DEPENDENCIES
100
149
  sqlite3
101
150
 
102
151
  BUNDLED WITH
103
- 2.2.10
152
+ 2.6.5
data/README.md CHANGED
@@ -116,37 +116,17 @@ Or install it yourself as:
116
116
  The preferred type of notifications can be configured with:
117
117
 
118
118
  * `Prosopite.min_n_queries`: Minimum number of N queries to report per N+1 case. Defaults to 2.
119
- * `Prosopite.raise = true`: Raise warnings as exceptions
120
- * `Prosopite.rails_logger = true`: Send warnings to the Rails log
121
- * `Prosopite.prosopite_logger = true`: Send warnings to `log/prosopite.log`
122
- * `Prosopite.stderr_logger = true`: Send warnings to STDERR
119
+ * `Prosopite.raise = true`: Raise warnings as exceptions. Defaults to `false`.
120
+ * `Prosopite.start_raise`: Raises warnings as exceptions from when this is called. Overrides `Proposite.raise`.
121
+ * `Propsoite.stop_raise`: Disables raising warnings as exceptions if previously enabled with `Proposite.start_raise`.
122
+ * `Prosopite.local_raise?`: Returns `true` if `Prosopite.start_raise` has been called previously.
123
+ * `Prosopite.rails_logger = true`: Send warnings to the Rails log. Defaults to `false`.
124
+ * `Prosopite.prosopite_logger = true`: Send warnings to `log/prosopite.log`. Defaults to `false`.
125
+ * `Prosopite.stderr_logger = true`: Send warnings to STDERR. Defaults to `false`.
123
126
  * `Prosopite.backtrace_cleaner = my_custom_backtrace_cleaner`: use a different [ActiveSupport::BacktraceCleaner](https://api.rubyonrails.org/classes/ActiveSupport/BacktraceCleaner.html). Defaults to `Rails.backtrace_cleaner`.
124
- * `Prosopite.custom_logger = my_custom_logger`:
127
+ * `Prosopite.custom_logger = my_custom_logger`: Set a custom logger. See the following section for the details. Defaults to `false`.
125
128
  * `Prosopite.enabled = true`: Enables or disables the gem. Defaults to `true`.
126
129
 
127
- ### Custom Logging Configuration
128
-
129
- You can supply a custom logger with the `Prosopite.custom_logger` setting.
130
-
131
- This is useful for circumstances where you don't want your logs to be
132
- highlighted with red, or you want logs sent to a custom location.
133
-
134
- One common scenario is that you may be generating json logs and sending them to
135
- Datadog, ELK stack, or similar, and don't want to have to remove the default red
136
- escaping data from messages sent to the Rails logger, or want to tag them
137
- differently with your own custom logger.
138
-
139
- ```ruby
140
- # Turns off logging with red highlights, but still sends them to the Rails logger
141
- Prosopite.custom_logger = Rails.logger
142
- ```
143
-
144
- ```ruby
145
- # Use a completely custom logging instance
146
- Prosopite.custom_logger = MyLoggerClass.new
147
-
148
- ```
149
-
150
130
  ## Development Environment Usage
151
131
 
152
132
  Prosopite auto-detection can be enabled on all controllers:
@@ -174,7 +154,7 @@ config.after_initialize do
174
154
  Prosopite.rails_logger = true
175
155
  end
176
156
  ```
177
-
157
+ ```
178
158
  ## Test Environment Usage
179
159
 
180
160
  Tests with N+1 queries can be configured to fail with:
@@ -315,6 +295,77 @@ Pauses can be ignored with `Prosopite.ignore_pauses = true` in case you want to
315
295
  An example of when you might use this is if you are [testing Active Jobs inline](https://guides.rubyonrails.org/testing.html#testing-jobs),
316
296
  and don't want to run Prosopite on background job code, just foreground app code. In that case you could write an [Active Job callback](https://edgeguides.rubyonrails.org/active_job_basics.html#callbacks) that pauses the scan while the job is running.
317
297
 
298
+ ## Local Raise
299
+
300
+ In some cases you may want to configure prosopite to not raise by default and only raise in certain scenarios.
301
+ In this example we scan on all controllers but also provide an API to only raise on specific actions.
302
+
303
+ ```ruby
304
+ Proposite.raise = false
305
+ ```
306
+
307
+ ```ruby
308
+ # app/controllers/application_controller.rb
309
+ class ApplicationController < ActionController::Base
310
+ def raise_on_n_plus_ones!(**options)
311
+ return if Rails.env.production?
312
+
313
+ prepend_around_action(:_raise_on_n_plus_ones, **options)
314
+ end
315
+
316
+ unless Rails.env.production?
317
+ around_action :n_plus_one_detection
318
+
319
+ def n_plus_one_detection
320
+ ...
321
+ end
322
+
323
+ def _raise_on_n_plus_ones
324
+ Proposite.start_raise
325
+ yield
326
+ ensure
327
+ Prosopite.stop_raise
328
+ end
329
+ end
330
+ end
331
+ ```
332
+ ```ruby
333
+ # app/controllers/books_controller.rb
334
+ class BooksController < ApplicationController
335
+ raise_on_n_plus_ones!(only: [:index])
336
+
337
+ def index
338
+ @books = Book.all.map(&:author) # This will raise N+1 errors
339
+ end
340
+
341
+ def show
342
+ @book = Book.find(params[:id])
343
+ @book.reviews.map(&:author) # This will not raise N+1 errors
344
+ end
345
+ end
346
+
347
+ ## Custom Logging Configuration
348
+
349
+ You can supply a custom logger with the `Prosopite.custom_logger` setting.
350
+
351
+ This is useful for circumstances where you don't want your logs to be
352
+ highlighted with red, or you want logs sent to a custom location.
353
+
354
+ One common scenario is that you may be generating json logs and sending them to
355
+ Datadog, ELK stack, or similar, and don't want to have to remove the default red
356
+ escaping data from messages sent to the Rails logger, or want to tag them
357
+ differently with your own custom logger.
358
+
359
+ ```ruby
360
+ # Turns off logging with red highlights, but still sends them to the Rails logger
361
+ Prosopite.custom_logger = Rails.logger
362
+ ```
363
+
364
+ ```ruby
365
+ # Use a completely custom logging instance
366
+ Prosopite.custom_logger = MyLoggerClass.new
367
+ ```
368
+
318
369
  ## Contributing
319
370
 
320
371
  Bug reports and pull requests are welcome on GitHub at https://github.com/charkost/prosopite.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Prosopite
4
- VERSION = "1.4.2"
4
+ VERSION = "2.1.2"
5
5
  end
data/lib/prosopite.rb CHANGED
@@ -113,6 +113,22 @@ module Prosopite
113
113
  tc[:prosopite_query_caller] = nil
114
114
  end
115
115
 
116
+ def start_raise
117
+ tc[:prosopite_local_raise] = true
118
+ end
119
+
120
+ def stop_raise
121
+ tc[:prosopite_local_raise] = false
122
+ end
123
+
124
+ def local_raise?
125
+ tc[:prosopite_local_raise] == true
126
+ end
127
+
128
+ def raise?
129
+ local_raise? || !!@raise
130
+ end
131
+
116
132
  def create_notifications
117
133
  tc[:prosopite_notifications] = {}
118
134
 
@@ -130,7 +146,7 @@ module Prosopite
130
146
 
131
147
  next unless queries.any?
132
148
 
133
- kaller = tc[:prosopite_query_caller][location_key]
149
+ kaller = tc[:prosopite_query_caller][location_key].map(&:to_s)
134
150
  allow_list = (@allow_stack_paths + DEFAULT_ALLOW_LIST)
135
151
  is_allowed = kaller.any? { |f| allow_list.any? { |s| f.match?(s) } }
136
152
 
@@ -144,7 +160,7 @@ module Prosopite
144
160
  end
145
161
 
146
162
  def fingerprint(query)
147
- db_adapter = ActiveRecord::Base.connection.adapter_name.downcase
163
+ db_adapter = ActiveRecord::Base.connection_db_config.adapter
148
164
  if db_adapter.include?('mysql') || db_adapter.include?('trilogy')
149
165
  mysql_fingerprint(query)
150
166
  else
@@ -212,9 +228,8 @@ module Prosopite
212
228
  @rails_logger ||= false
213
229
  @stderr_logger ||= false
214
230
  @prosopite_logger ||= false
215
- @raise ||= false
216
231
 
217
- notifications_str = ''
232
+ notifications_str = String.new('')
218
233
 
219
234
  tc[:prosopite_notifications].each do |queries, kaller|
220
235
  notifications_str << "N+1 queries detected:\n"
@@ -241,7 +256,7 @@ module Prosopite
241
256
  end
242
257
  end
243
258
 
244
- raise NPlusOneQueriesError.new(notifications_str) if @raise
259
+ raise NPlusOneQueriesError.new(notifications_str) if raise?
245
260
  end
246
261
 
247
262
  def red(str)
@@ -261,14 +276,20 @@ module Prosopite
261
276
  sql, name = data[:sql], data[:name]
262
277
 
263
278
  if scan? && name != "SCHEMA" && sql.include?('SELECT') && data[:cached].nil? && !ignore_query?(sql)
264
- query_caller = caller
265
- location_key = Digest::SHA256.hexdigest(query_caller.join)
279
+ query_caller = caller_locations
280
+ # Calculate the location key with as few allocations as possible
281
+ location_key = [].tap do |array|
282
+ query_caller.each do |loc|
283
+ array << loc.path
284
+ array << loc.lineno
285
+ end
286
+ end.hash
266
287
 
267
288
  tc[:prosopite_query_counter][location_key] += 1
268
289
  tc[:prosopite_query_holder][location_key] << sql
269
290
 
270
291
  if tc[:prosopite_query_counter][location_key] > 1
271
- tc[:prosopite_query_caller][location_key] = query_caller.dup
292
+ tc[:prosopite_query_caller][location_key] = query_caller
272
293
  end
273
294
  end
274
295
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prosopite
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mpampis Kostas
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-14 00:00:00.000000000 Z
11
+ date: 2025-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry
@@ -133,7 +133,7 @@ licenses:
133
133
  metadata:
134
134
  homepage_uri: https://github.com/charkost/prosopite
135
135
  source_code_uri: https://github.com/charkost/prosopite
136
- post_install_message:
136
+ post_install_message:
137
137
  rdoc_options: []
138
138
  require_paths:
139
139
  - lib
@@ -148,8 +148,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
148
  - !ruby/object:Gem::Version
149
149
  version: '0'
150
150
  requirements: []
151
- rubygems_version: 3.4.6
152
- signing_key:
151
+ rubygems_version: 3.5.3
152
+ signing_key:
153
153
  specification_version: 4
154
154
  summary: N+1 auto-detection for Rails with zero false positives / false negatives
155
155
  test_files: []