arlequin 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64dab272387e862b66546a8de88779cc36ffbdfaa0a6082125b8404509fec181
4
- data.tar.gz: 0c1d6fe9c4af985484476e81645ce54a1a1391ed19765276dd9d1915bc12b514
3
+ metadata.gz: 3083b16c52cd3b8f777180ee21b6f838768bb8da81bd5079312fad9896ba14a7
4
+ data.tar.gz: c6daedc9d726407f3a26aabae5aa38865d2470cb1e24cf9f1fffd47d4106d761
5
5
  SHA512:
6
- metadata.gz: 6dab0e3d89077eccf1a50591045e75720bade0b638ff4ab5c2e892c7297b6b12c314cdb25047094b50ac4a45895e18ebb6e2a742409dc290cb7d8620fa911995
7
- data.tar.gz: 194ba82a1169e3abec5d4114bd1fae8e3c51b169103f8ae3c2ae8fea8cb4bedc596e0681fbb75840fa6b94f0ea34e1e8acf431eb71e6ddf5efb9097fca91f503
6
+ metadata.gz: 8ae1b8e8d9d99928a76d562bbdfef3b2effe853f60fb2cfe2943a098532d919cb99a9c7f4b79396d09d88f4361794cd9656e62850c0465f370b8fc7dc6b1cfd3
7
+ data.tar.gz: a361f0f8055ed4e5f97580ede4ba096c6d6edb3638fe3c51e6078e54e833345a6f78f9d8524b118a78f860224b326e41d9a45d36e643db1917ea76c18c214f3f
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ /doc/
2
+ /tmp/
3
+ /log/
4
+ *.gem
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ inherit_gem:
2
+ rubocop-rails-omakase: rubocop.yml
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "http://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "rubocop-rails-omakase", require: false, group: [ :development ]
data/Gemfile.lock ADDED
@@ -0,0 +1,232 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ arlequin (0.1.1)
5
+ rails (>= 5.0)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ actioncable (7.1.3.2)
11
+ actionpack (= 7.1.3.2)
12
+ activesupport (= 7.1.3.2)
13
+ nio4r (~> 2.0)
14
+ websocket-driver (>= 0.6.1)
15
+ zeitwerk (~> 2.6)
16
+ actionmailbox (7.1.3.2)
17
+ actionpack (= 7.1.3.2)
18
+ activejob (= 7.1.3.2)
19
+ activerecord (= 7.1.3.2)
20
+ activestorage (= 7.1.3.2)
21
+ activesupport (= 7.1.3.2)
22
+ mail (>= 2.7.1)
23
+ net-imap
24
+ net-pop
25
+ net-smtp
26
+ actionmailer (7.1.3.2)
27
+ actionpack (= 7.1.3.2)
28
+ actionview (= 7.1.3.2)
29
+ activejob (= 7.1.3.2)
30
+ activesupport (= 7.1.3.2)
31
+ mail (~> 2.5, >= 2.5.4)
32
+ net-imap
33
+ net-pop
34
+ net-smtp
35
+ rails-dom-testing (~> 2.2)
36
+ actionpack (7.1.3.2)
37
+ actionview (= 7.1.3.2)
38
+ activesupport (= 7.1.3.2)
39
+ nokogiri (>= 1.8.5)
40
+ racc
41
+ rack (>= 2.2.4)
42
+ rack-session (>= 1.0.1)
43
+ rack-test (>= 0.6.3)
44
+ rails-dom-testing (~> 2.2)
45
+ rails-html-sanitizer (~> 1.6)
46
+ actiontext (7.1.3.2)
47
+ actionpack (= 7.1.3.2)
48
+ activerecord (= 7.1.3.2)
49
+ activestorage (= 7.1.3.2)
50
+ activesupport (= 7.1.3.2)
51
+ globalid (>= 0.6.0)
52
+ nokogiri (>= 1.8.5)
53
+ actionview (7.1.3.2)
54
+ activesupport (= 7.1.3.2)
55
+ builder (~> 3.1)
56
+ erubi (~> 1.11)
57
+ rails-dom-testing (~> 2.2)
58
+ rails-html-sanitizer (~> 1.6)
59
+ activejob (7.1.3.2)
60
+ activesupport (= 7.1.3.2)
61
+ globalid (>= 0.3.6)
62
+ activemodel (7.1.3.2)
63
+ activesupport (= 7.1.3.2)
64
+ activerecord (7.1.3.2)
65
+ activemodel (= 7.1.3.2)
66
+ activesupport (= 7.1.3.2)
67
+ timeout (>= 0.4.0)
68
+ activestorage (7.1.3.2)
69
+ actionpack (= 7.1.3.2)
70
+ activejob (= 7.1.3.2)
71
+ activerecord (= 7.1.3.2)
72
+ activesupport (= 7.1.3.2)
73
+ marcel (~> 1.0)
74
+ activesupport (7.1.3.2)
75
+ base64
76
+ bigdecimal
77
+ concurrent-ruby (~> 1.0, >= 1.0.2)
78
+ connection_pool (>= 2.2.5)
79
+ drb
80
+ i18n (>= 1.6, < 2)
81
+ minitest (>= 5.1)
82
+ mutex_m
83
+ tzinfo (~> 2.0)
84
+ ast (2.4.2)
85
+ base64 (0.2.0)
86
+ bigdecimal (3.1.8)
87
+ builder (3.3.0)
88
+ concurrent-ruby (1.3.3)
89
+ connection_pool (2.4.1)
90
+ crass (1.0.6)
91
+ date (3.3.4)
92
+ drb (2.2.1)
93
+ erubi (1.13.0)
94
+ globalid (1.2.1)
95
+ activesupport (>= 6.1)
96
+ i18n (1.14.5)
97
+ concurrent-ruby (~> 1.0)
98
+ io-console (0.7.2)
99
+ irb (1.14.0)
100
+ rdoc (>= 4.0.0)
101
+ reline (>= 0.4.2)
102
+ json (2.7.2)
103
+ language_server-protocol (3.17.0.3)
104
+ loofah (2.22.0)
105
+ crass (~> 1.0.2)
106
+ nokogiri (>= 1.12.0)
107
+ mail (2.8.1)
108
+ mini_mime (>= 0.1.1)
109
+ net-imap
110
+ net-pop
111
+ net-smtp
112
+ marcel (1.0.4)
113
+ mini_mime (1.1.5)
114
+ minitest (5.24.1)
115
+ mutex_m (0.2.0)
116
+ net-imap (0.4.14)
117
+ date
118
+ net-protocol
119
+ net-pop (0.1.2)
120
+ net-protocol
121
+ net-protocol (0.2.2)
122
+ timeout
123
+ net-smtp (0.5.0)
124
+ net-protocol
125
+ nio4r (2.7.3)
126
+ nokogiri (1.16.6-arm64-darwin)
127
+ racc (~> 1.4)
128
+ parallel (1.25.1)
129
+ parser (3.3.4.0)
130
+ ast (~> 2.4.1)
131
+ racc
132
+ psych (5.1.2)
133
+ stringio
134
+ racc (1.8.0)
135
+ rack (3.1.7)
136
+ rack-session (2.0.0)
137
+ rack (>= 3.0.0)
138
+ rack-test (2.1.0)
139
+ rack (>= 1.3)
140
+ rackup (2.1.0)
141
+ rack (>= 3)
142
+ webrick (~> 1.8)
143
+ rails (7.1.3.2)
144
+ actioncable (= 7.1.3.2)
145
+ actionmailbox (= 7.1.3.2)
146
+ actionmailer (= 7.1.3.2)
147
+ actionpack (= 7.1.3.2)
148
+ actiontext (= 7.1.3.2)
149
+ actionview (= 7.1.3.2)
150
+ activejob (= 7.1.3.2)
151
+ activemodel (= 7.1.3.2)
152
+ activerecord (= 7.1.3.2)
153
+ activestorage (= 7.1.3.2)
154
+ activesupport (= 7.1.3.2)
155
+ bundler (>= 1.15.0)
156
+ railties (= 7.1.3.2)
157
+ rails-dom-testing (2.2.0)
158
+ activesupport (>= 5.0.0)
159
+ minitest
160
+ nokogiri (>= 1.6)
161
+ rails-html-sanitizer (1.6.0)
162
+ loofah (~> 2.21)
163
+ nokogiri (~> 1.14)
164
+ railties (7.1.3.2)
165
+ actionpack (= 7.1.3.2)
166
+ activesupport (= 7.1.3.2)
167
+ irb
168
+ rackup (>= 1.0.0)
169
+ rake (>= 12.2)
170
+ thor (~> 1.0, >= 1.2.2)
171
+ zeitwerk (~> 2.6)
172
+ rainbow (3.1.1)
173
+ rake (13.2.1)
174
+ rdoc (6.7.0)
175
+ psych (>= 4.0.0)
176
+ regexp_parser (2.9.2)
177
+ reline (0.5.9)
178
+ io-console (~> 0.5)
179
+ rexml (3.3.2)
180
+ strscan
181
+ rubocop (1.65.0)
182
+ json (~> 2.3)
183
+ language_server-protocol (>= 3.17.0)
184
+ parallel (~> 1.10)
185
+ parser (>= 3.3.0.2)
186
+ rainbow (>= 2.2.2, < 4.0)
187
+ regexp_parser (>= 2.4, < 3.0)
188
+ rexml (>= 3.2.5, < 4.0)
189
+ rubocop-ast (>= 1.31.1, < 2.0)
190
+ ruby-progressbar (~> 1.7)
191
+ unicode-display_width (>= 2.4.0, < 3.0)
192
+ rubocop-ast (1.31.3)
193
+ parser (>= 3.3.1.0)
194
+ rubocop-minitest (0.35.1)
195
+ rubocop (>= 1.61, < 2.0)
196
+ rubocop-ast (>= 1.31.1, < 2.0)
197
+ rubocop-performance (1.21.1)
198
+ rubocop (>= 1.48.1, < 2.0)
199
+ rubocop-ast (>= 1.31.1, < 2.0)
200
+ rubocop-rails (2.25.1)
201
+ activesupport (>= 4.2.0)
202
+ rack (>= 1.1)
203
+ rubocop (>= 1.33.0, < 2.0)
204
+ rubocop-ast (>= 1.31.1, < 2.0)
205
+ rubocop-rails-omakase (1.0.0)
206
+ rubocop
207
+ rubocop-minitest
208
+ rubocop-performance
209
+ rubocop-rails
210
+ ruby-progressbar (1.13.0)
211
+ stringio (3.1.1)
212
+ strscan (3.1.0)
213
+ thor (1.3.1)
214
+ timeout (0.4.1)
215
+ tzinfo (2.0.6)
216
+ concurrent-ruby (~> 1.0)
217
+ unicode-display_width (2.5.0)
218
+ webrick (1.8.1)
219
+ websocket-driver (0.7.6)
220
+ websocket-extensions (>= 0.1.0)
221
+ websocket-extensions (0.1.5)
222
+ zeitwerk (2.6.16)
223
+
224
+ PLATFORMS
225
+ arm64-darwin-22
226
+
227
+ DEPENDENCIES
228
+ arlequin!
229
+ rubocop-rails-omakase
230
+
231
+ BUNDLED WITH
232
+ 2.4.19
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Arlequin
2
+
3
+ **Arlequin** is a Ruby gem that helps detect and warn about N+1 SQL queries in Rails applications. It includes middleware that integrates seamlessly with your Rails application to improve performance by identifying inefficient database queries.
4
+
5
+ ## Features
6
+
7
+ - **N+1 Query Detection**: Automatically detects and warns about N+1 query problems.
8
+ - **Integration**: Easy integration with Rails applications through a Railtie.
9
+ - **Performance Insights**: Provides detailed insights into query performance issues.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'arlequin', group: [ :development ]
17
+ ```
18
+
19
+ Then execute:
20
+
21
+ ```bash
22
+ $ bundle install
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Basic Setup
28
+
29
+ To start using Arlequin, simply add the following to your Rails application’s configuration file (config/application.rb):
30
+
31
+ ```ruby
32
+ require 'arlequin'
33
+ ```
34
+
35
+ ### Middleware
36
+ Arlequin integrates with Rails as middleware. It will automatically start monitoring SQL queries for N+1 problems.
37
+ No additional setup is required beyond including it in your Gemfile and configuration.
38
+
39
+ ## Example
40
+
41
+ Suppose you have a Rails application with a Post model that has many Comments. If you inadvertently write code like:
42
+
43
+ ```ruby
44
+ @posts = Post.all
45
+ @posts.each do |post|
46
+ puts post.comments.count
47
+ end
48
+ ```
49
+
50
+ Arlequin will detect this N+1 query issue and warn you about it in the UI.
51
+
52
+ ## Development
53
+
54
+ To contribute to Arlequin, clone the repository and run the test suite:
55
+
56
+ ```bash
57
+ git clone https://github.com/dominicgoulet/arlequin.git
58
+ cd arlequin
59
+ bundle install
60
+ rake test
61
+ ```
62
+
63
+ Make sure to write tests for any new features or bug fixes you add.
64
+ Follow the existing coding style and conventions used in the project.
65
+
66
+ ## Contributing
67
+
68
+ We welcome contributions! Please submit pull requests and issues through GitHub.
69
+ Ensure your code adheres to the project’s style and includes appropriate tests.
70
+
71
+ ## License
72
+
73
+ Arlequin is released under the MIT License.
74
+
75
+ ## Contact
76
+
77
+ For any questions or feedback, feel free to reach out to us at [dominic@dominicgoulet.com].
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/test_task"
4
+
5
+ Minitest::TestTask.create(:test) do |t|
6
+ t.libs << "test"
7
+ t.libs << "lib"
8
+ t.warning = false
9
+ t.test_globs = [ "test/**/*_test.rb" ]
10
+ end
11
+
12
+ task default: :test
data/arlequin.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "arlequin"
5
+ s.version = "0.1.2"
6
+ s.summary = "Arlequin performance logger gem"
7
+ s.description = "Performance Logger Gem"
8
+
9
+ s.license = "MIT"
10
+
11
+ s.author = "Dominic Goulet"
12
+ s.email = "dominic@dominicgoulet.com"
13
+ s.homepage = "https://github.com/dominicgoulet/arlequin"
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ s.files = Dir.chdir(File.expand_path(__dir__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|translation)/}) }
19
+ end
20
+
21
+ s.metadata = {
22
+ "source_code_uri" => s.homepage
23
+ }
24
+
25
+ s.add_dependency "rails", ">= 5.0"
26
+ end
@@ -1,40 +1,107 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Arlequin
4
+ ##
5
+ # Middleware to detect and warn about N+1 queries in a Rails application.
4
6
  class Middleware
7
+ ##
8
+ # Initializes the middleware with the given app.
9
+ #
10
+ # @param app [Object] The Rack application this middleware wraps.
5
11
  def initialize(app)
6
12
  @app = app
7
13
  end
8
14
 
15
+ ##
16
+ # Processes the incoming request, detects N+1 queries, and injects a warning
17
+ # into the HTML response if necessary.
18
+ #
19
+ # @param env [Hash] The Rack environment.
20
+ # @return [Array] The status, headers, and response of the Rack application.
9
21
  def call(env)
10
- # Set up a query counter
11
22
  @queries = Hash.new { |hash, key| hash[key] = 0 }
12
-
13
23
  ActiveSupport::Notifications.subscribe("sql.active_record", &method(:on_sql))
14
24
 
15
25
  status, headers, response = @app.call(env)
16
26
 
17
- # Detect and log N+1 queries
18
- log_n_plus_one_warnings
27
+ if n_plus_one_detected?
28
+ html_fragment = generate_html_warning
29
+ response = inject_html_fragment(response, html_fragment)
30
+ end
31
+
32
+ ActiveSupport::Notifications.unsubscribe("sql.active_record")
19
33
 
20
34
  [ status, headers, response ]
21
35
  end
22
36
 
23
37
  private
24
38
 
39
+ ##
40
+ # Callback for ActiveSupport notifications on SQL queries.
41
+ #
42
+ # @param _name [String] The event name.
43
+ # @param _start [Time] The start time of the event.
44
+ # @param _finish [Time] The finish time of the event.
45
+ # @param _id [String] The event ID.
46
+ # @param payload [Hash] The event payload containing SQL information.
25
47
  def on_sql(_name, _start, _finish, _id, payload)
26
- return if payload[:name] == "SCHEMA" # Skip schema queries
48
+ return if payload[:name] == "SCHEMA"
27
49
 
28
50
  query = payload[:sql]
29
51
  @queries[query] += 1
30
52
  end
31
53
 
32
- def log_n_plus_one_warnings
54
+ ##
55
+ # Generates warnings for detected N+1 queries.
56
+ #
57
+ # @return [Array<String>] The list of N+1 query warnings.
58
+ def n_plus_one_warnings
59
+ warnings = []
60
+
33
61
  @queries.each do |query, count|
34
62
  if count > 1
35
- Rails.logger.warn("N+1 query detected: #{query}, executed #{count} times")
63
+ warnings << "N+1 query detected: #{query}, executed #{count} times"
36
64
  end
37
65
  end
66
+
67
+ warnings
68
+ end
69
+
70
+ ##
71
+ # Checks if any N+1 queries have been detected.
72
+ #
73
+ # @return [Boolean] True if N+1 queries are detected, otherwise false.
74
+ def n_plus_one_detected?
75
+ @queries.values.any? { |count| count > 1 }
76
+ end
77
+
78
+ ##
79
+ # Generates an HTML warning for detected N+1 queries.
80
+ #
81
+ # @return [String] The HTML warning to be injected into the response.
82
+ def generate_html_warning
83
+ "<div style='z-index: 9999; position: fixed; bottom: 0; left: 0; width: 100%; background-color: blue; color: white; text-align: center; padding: 5px;'>" +
84
+ "N+1 query detected! Check your queries. " +
85
+ n_plus_one_warnings.join("<br>") +
86
+ "</div>"
87
+ end
88
+
89
+ ##
90
+ # Injects an HTML fragment into the response body.
91
+ #
92
+ # @param response [Array<String>] The original response body.
93
+ # @param fragment [String] The HTML fragment to inject.
94
+ # @return [Array<String>] The modified response body.
95
+ def inject_html_fragment(response, fragment)
96
+ body = + ""
97
+
98
+ response.each do |part|
99
+ body << part
100
+ end
101
+
102
+ body.sub!("</body>", "#{fragment}</body>")
103
+
104
+ [ body ]
38
105
  end
39
106
  end
40
107
  end
data/lib/arlequin.rb CHANGED
@@ -1,7 +1,56 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Arlequin
3
+ require "active_support/all"
4
+ require "arlequin/middleware"
5
+
6
+ module Arlequin
7
+ # The `Railtie` class integrates the Arlequin middleware into Rails applications.
8
+ #
9
+ # This Railtie automatically adds the Arlequin middleware to the Rails middleware stack,
10
+ # but it is intended to be used only in the development environment. It helps in monitoring
11
+ # and providing warnings about N+1 SQL queries during development.
12
+ #
13
+ # ## Configuration
14
+ #
15
+ # To use Arlequin in your Rails application, you should include it in the development group
16
+ # of your Gemfile and require it in the application configuration.
17
+ #
18
+ # Example Gemfile configuration:
19
+ #
20
+ # group :development do
21
+ # gem 'arlequin'
22
+ # end
23
+ #
24
+ # To ensure the middleware is used in development, require the Railtie in your application:
25
+ #
26
+ # # In `config/application.rb`
27
+ # require "arlequin/railtie"
28
+ #
29
+ # module YourApp
30
+ # class Application < Rails::Application
31
+ # # other configurations
32
+ # # Ensure that the Railtie is required
33
+ # end
34
+ # end
35
+ #
36
+ # This setup will automatically add the Arlequin middleware to the middleware stack
37
+ # when running in development mode. The middleware will then be used to detect and
38
+ # provide warnings for N+1 SQL queries.
39
+ #
40
+ # ## Usage
41
+ #
42
+ # Once configured, the Arlequin middleware will monitor SQL queries and inject a warning
43
+ # into the response HTML if N+1 queries are detected.
44
+ #
45
+ # The Railtie is part of the Arlequin gem and is designed to integrate seamlessly into
46
+ # Rails applications for development purposes, facilitating easier identification and
47
+ # resolution of N+1 query issues.
4
48
  class Railtie < Rails::Railtie
49
+ # Initializes the Railtie and configures the Rails application to use the Arlequin middleware.
50
+ #
51
+ # This method is called during the initialization process of the Rails application.
52
+ # It adds the Arlequin middleware to the middleware stack in development environments,
53
+ # allowing it to intercept requests and monitor SQL queries.
5
54
  initializer "arlequin.configure_rails_initialization" do |app|
6
55
  app.middleware.use Arlequin::Middleware
7
56
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arlequin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominic Goulet
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-10 00:00:00.000000000 Z
11
+ date: 2024-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -25,17 +25,25 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '5.0'
27
27
  description: Performance Logger Gem
28
- email: dominic.goulet@gmail.com
28
+ email: dominic@dominicgoulet.com
29
29
  executables: []
30
30
  extensions: []
31
31
  extra_rdoc_files: []
32
32
  files:
33
+ - ".gitignore"
34
+ - ".rubocop.yml"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - README.md
38
+ - Rakefile
39
+ - arlequin.gemspec
33
40
  - lib/arlequin.rb
34
41
  - lib/arlequin/middleware.rb
35
- homepage: https://rubygems.org/gems/arlequin
42
+ homepage: https://github.com/dominicgoulet/arlequin
36
43
  licenses:
37
44
  - MIT
38
- metadata: {}
45
+ metadata:
46
+ source_code_uri: https://github.com/dominicgoulet/arlequin
39
47
  post_install_message:
40
48
  rdoc_options: []
41
49
  require_paths: