active-query-explorer 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a2ef157353a28e6c13fa4d6a3e3bb51936219ca8824de578a276c1339dbb37b8
4
+ data.tar.gz: 2b83cff84f6190b827da21c5ab071492ddfe6715689a997b9e2d77a27d7a6824
5
+ SHA512:
6
+ metadata.gz: 58bf29b6f5a7c150f70e5d51d688fef495749f8227c8cadcfb2723baecc59fb67cc3fc731631440ccb55bbae7638172e9eaccea863e172e6999cec271cfdf6d3
7
+ data.tar.gz: 92458577f9d13b22250b8ffe96b427d1e89f80562cfb0cc0061d3090243146a62950a5ee2aee2668c55a0cd2f95dec086972d2d031559f721f1544ce7364b288
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-19
11
+
12
+ ### Added
13
+ - Initial release of ActiveQueryExplorer gem
14
+ - Mountable Rails engine for browsing and executing ActiveQuery query objects
15
+ - Web GUI for discovering all registered ActiveQuery objects
16
+ - Display of query metadata (arguments, types, defaults)
17
+ - Execute queries with parameters from the browser
18
+ - Plain text format endpoint for AI-consumable query catalog
19
+ - Support for Rails 6.1+ and Ruby 3.2+
20
+
21
+ [Unreleased]: https://github.com/matiasasis/active-query-explorer/compare/v0.1.0...HEAD
22
+ [0.1.0]: https://github.com/matiasasis/active-query-explorer/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Matias Asis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # Active Query Explorer
2
+
3
+ A mountable Rails engine that provides a web UI for browsing, searching, and executing [Active Query](https://github.com/matiasasis/active-query) objects registered in your application.
4
+
5
+ ## Overview
6
+
7
+ Active Query Explorer discovers all classes registered in `ActiveQuery::Base.registry`, groups them by namespace, and presents them in an interactive interface. Developers can browse query definitions, inspect parameters (names, types, defaults, optionality), and execute queries directly from the browser.
8
+
9
+ It is designed for Rails applications that use the `active-query` gem to define query objects. If your app has query classes inheriting from `ActiveQuery::Base`, this engine makes them discoverable and runnable without writing any additional code.
10
+
11
+ ## Installation
12
+
13
+ Add to your Gemfile. The gem is not yet published to RubyGems, so reference it via Git or a local path:
14
+
15
+ ```ruby
16
+ # Via Git
17
+ gem "active-query-explorer", git: "https://github.com/matiasasis/active-query-explorer.git"
18
+
19
+ # Or via local path during development
20
+ gem "active-query-explorer", path: "../active-query-explorer"
21
+ ```
22
+
23
+ Then run:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ **Requirements:** Ruby >= 3.2, Rails >= 6.1 and < 9.0, `active-query` >= 0.1.3.
30
+
31
+ ## Mounting the Engine
32
+
33
+ Add to your `config/routes.rb`:
34
+
35
+ ```ruby
36
+ mount ActiveQueryExplorer::Engine => "/queries", as: "active_query_explorer"
37
+ ```
38
+
39
+ After starting your Rails server, visit `http://localhost:3000/queries` to access the UI.
40
+
41
+ The engine is isolated-namespaced (`ActiveQueryExplorer`), so it will not conflict with your application's routes or controllers.
42
+
43
+ ## Usage
44
+
45
+ ### Web UI
46
+
47
+ The HTML interface at your mount path provides:
48
+
49
+ - **Sidebar** -- Query classes grouped by namespace, with collapsible sections and count badges. Clicking a class scrolls to its detail card.
50
+ - **Search** -- Full-text filtering across class names, query names, descriptions, and parameter names. Focus with `Cmd+K` / `Ctrl+K`.
51
+ - **Filters** -- Namespace dropdown, parameter presence toggle (all / with params / without params).
52
+ - **Query cards** -- Expandable cards showing each query's description, parameters (name, type, required/optional, default value), and source file location.
53
+ - **Inline execution** -- Form fields rendered per-parameter with type labels. Submit to execute the query and see results with timing metadata.
54
+
55
+ ### Example Flow
56
+
57
+ 1. Visit `/queries`
58
+ 2. Browse or search for a query (e.g., `Billing::InvoiceQuery#overdue`)
59
+ 3. Expand the query card to see its parameters
60
+ 4. Fill in parameter values and click Execute
61
+ 5. View the returned result set inline
62
+
63
+ ## API / Formats
64
+
65
+ The index endpoint supports three response formats:
66
+
67
+ ### HTML (default)
68
+
69
+ ```
70
+ GET /queries
71
+ ```
72
+
73
+ The interactive UI described above.
74
+
75
+ ### JSON
76
+
77
+ ```
78
+ GET /queries.json
79
+ ```
80
+
81
+ Returns the full query catalog as structured JSON -- an array of namespace groups, each containing query objects with their class names, source locations, and query definitions (name, description, parameters).
82
+
83
+ Useful for building custom tooling or dashboards.
84
+
85
+ ### Text
86
+
87
+ ```
88
+ GET /queries.text
89
+ ```
90
+
91
+ Returns an AI-consumable plain text catalog. Each query is rendered as a structured block:
92
+
93
+ ```
94
+ === QUERY START ===
95
+ name: Billing::InvoiceQuery#overdue
96
+ namespace: Billing
97
+ description: Find overdue invoices
98
+ returns: unknown
99
+ side_effects: unknown
100
+ idempotent: unknown
101
+ safety: unknown
102
+
103
+ inputs:
104
+ - name: days
105
+ type: Integer
106
+ required: true
107
+ default: none
108
+ === QUERY END ===
109
+ ```
110
+
111
+ Useful for including query definitions in LLM context or generating documentation.
112
+
113
+ ### Execute Endpoint
114
+
115
+ ```
116
+ POST /queries/execute
117
+ Content-Type: application/json
118
+
119
+ {
120
+ "query_class": "Billing::InvoiceQuery",
121
+ "query_name": "overdue",
122
+ "args": { "days": 30 }
123
+ }
124
+ ```
125
+
126
+ Returns `{ "result": <serialized_data> }` on success, or `{ "error": "message" }` on failure. CSRF token is required (the UI handles this automatically).
127
+
128
+ ## Configuration
129
+
130
+ Create an initializer (e.g., `config/initializers/active_query_explorer.rb`):
131
+
132
+ ```ruby
133
+ ActiveQueryExplorer.result_limit = 200 # Max records returned (default: 100)
134
+ ActiveQueryExplorer.query_paths = %w[queries] # Subdirectories under app/ to eager-load (default: ["queries", "query_objects"])
135
+ ```
136
+
137
+ ### Swappable Services
138
+
139
+ The discovery, execution, and serialization layers can be replaced:
140
+
141
+ ```ruby
142
+ ActiveQueryExplorer.discovery_class = MyCustomDiscovery
143
+ ActiveQueryExplorer.executor_class = MyCustomExecutor
144
+ ActiveQueryExplorer.serializer_class = MyCustomSerializer
145
+ ```
146
+
147
+ Each must implement the same interface as the default classes (see Architecture below).
148
+
149
+ ## Architecture
150
+
151
+ ```
152
+ lib/
153
+ active_query_explorer.rb # Module entry point, configuration accessors
154
+ active_query_explorer/
155
+ engine.rb # Rails engine definition, eager-loading initializer
156
+ query_discovery.rb # Reads ActiveQuery::Base.registry, groups by namespace
157
+ query_executor.rb # Coerces args, whitelists params, calls query methods
158
+ result_serializer.rb # Serializes AR relations, records, scalars, enumerables
159
+ query_text_formatter.rb # Generates plain text query catalog
160
+ version.rb # VERSION = "0.1.0"
161
+
162
+ app/
163
+ controllers/active_query_explorer/
164
+ queries_controller.rb # index (html/json/text) + execute (json)
165
+ views/active_query_explorer/queries/
166
+ index.html.erb # Full UI (HTML + CSS + JS, self-contained)
167
+
168
+ config/
169
+ routes.rb # Engine routes
170
+ ```
171
+
172
+ ### Discovery Flow
173
+
174
+ 1. On Rails boot, the engine's `eager_load_queries` initializer loads all Ruby files from `app/queries/`, `app/query_objects/`, and their Packwerk equivalents (`packs/*/app/queries/`, `packs/*/app/query_objects/`).
175
+ 2. When the index action is hit, `QueryDiscovery#grouped_queries` reads `ActiveQuery::Base.registry`, filters to Class entries, extracts each class's `.queries` definitions, and groups them by namespace (derived from the class name).
176
+ 3. Each query definition includes its name, description, and parameter definitions (`args_def`).
177
+
178
+ ### Execution Flow
179
+
180
+ 1. Controller validates `query_class` and `query_name` against `VALID_QUERY_NAME` regex (`/\A[a-zA-Z_]\w*\z/`).
181
+ 2. `QueryDiscovery` resolves the class and query definition.
182
+ 3. `QueryExecutor` whitelists arguments against `args_def` keys, coerces types via `ActiveQuery::TypeRegistry.coerce`, and calls the query method via `public_send`.
183
+ 4. `ResultSerializer` converts the result (AR relation, record, scalar, or enumerable) to JSON-safe output, applying `result_limit`.
184
+
185
+ ## Adding Query Objects
186
+
187
+ Query objects are discovered automatically. To add a new one:
188
+
189
+ 1. Create a class that inherits from `ActiveQuery::Base` in `app/queries/` (or `app/query_objects/`):
190
+
191
+ ```ruby
192
+ # app/queries/billing/invoice_query.rb
193
+ class Billing::InvoiceQuery < ActiveQuery::Base
194
+ query :overdue,
195
+ description: "Find invoices past due date",
196
+ args_def: {
197
+ days: { type: Integer, optional: false },
198
+ status: { type: String, optional: true, default: "pending" }
199
+ }
200
+
201
+ def self.overdue(args = {})
202
+ Invoice.where("due_date < ?", args[:days].days.ago)
203
+ .where(status: args[:status])
204
+ end
205
+ end
206
+ ```
207
+
208
+ 2. The class registers itself in `ActiveQuery::Base.registry` (handled by the `active-query` gem).
209
+ 3. Restart your Rails server (or, in development, the class will be loaded on the next request if eager loading is configured).
210
+ 4. Visit the explorer UI -- your query appears grouped under its namespace.
211
+
212
+ **Packwerk support:** If your app uses Packwerk, place query objects in `packs/<pack_name>/app/queries/` and they will be discovered automatically.
213
+
214
+ ## Development
215
+
216
+ ### Setup
217
+
218
+ ```bash
219
+ git clone <repo-url>
220
+ cd active-query-explorer
221
+ bundle install
222
+ ```
223
+
224
+ ### Running Tests
225
+
226
+ ```bash
227
+ bundle exec rspec
228
+ ```
229
+
230
+ Tests use an in-memory SQLite database. The test suite covers configuration, discovery, execution, serialization, the controller (including input validation and error handling), and the text formatter.
231
+
232
+ CI runs on Ruby 3.2, 3.3, and 3.4 via GitHub Actions.
233
+
234
+ ## Known Gaps
235
+
236
+ - **Authentication/authorization:** The engine does not include any access control. You must restrict access yourself (e.g., via a routing constraint or middleware). Since the execute endpoint runs arbitrary registered queries, this is important in production.
237
+ - **Not published to RubyGems:** Must be referenced via Git or path in your Gemfile.
238
+ - **Query interface assumptions:** The engine assumes query classes respond to `.queries` returning an array with `:name`, `:description`, and `:args_def` keys. This is dictated by the `active-query` gem's API -- refer to its documentation for the definitive contract.
239
+ - **Text format metadata:** The `returns`, `side_effects`, `idempotent`, and `safety` fields in the text format are always `unknown` -- the `active-query` gem does not currently expose this metadata.
240
+ - **Result limit is global:** `result_limit` applies to all queries uniformly; there is no per-query override.
241
+
242
+ ## License
243
+
244
+ MIT License. See [LICENSE](LICENSE) for details.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/active_query_explorer/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "active-query-explorer"
7
+ spec.version = ActiveQueryExplorer::VERSION
8
+ spec.authors = ["Matias Asis"]
9
+ spec.email = ["matiasis.90@gmail.com"]
10
+ spec.summary = "A mountable Rails engine for browsing and executing ActiveQuery query objects."
11
+ spec.description = "ActiveQuery Explorer provides a web GUI (similar to GraphiQL) that discovers all registered ActiveQuery objects, displays their metadata, and allows executing them with parameters."
12
+ spec.homepage = "https://github.com/matiasasis/active-query-explorer"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.2.0"
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/matiasasis/active-query-explorer"
17
+ spec.metadata["bug_tracker_uri"] = "https://github.com/matiasasis/active-query-explorer/issues"
18
+ spec.metadata["changelog_uri"] = "https://github.com/matiasasis/active-query-explorer/blob/main/CHANGELOG.md"
19
+ spec.metadata["rubygems_mfa_required"] = "true"
20
+
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (File.expand_path(f) == __FILE__) ||
24
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) ||
25
+ f.match?(/\.gem\z/)
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "active-query", "~> 0.1", ">= 0.1.3"
33
+ spec.add_dependency "actionpack", ">= 6.1", "< 9.0"
34
+ spec.add_dependency "railties", ">= 6.1", "< 9.0"
35
+
36
+ spec.add_development_dependency "rake", "~> 13.0"
37
+ spec.add_development_dependency "rspec", "~> 3.9"
38
+ spec.add_development_dependency "activerecord", ">= 6.1", "< 9.0"
39
+ spec.add_development_dependency "sqlite3", '>= 1.5.1', '< 3.0'
40
+ end