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 +7 -0
- data/CHANGELOG.md +22 -0
- data/LICENSE.txt +21 -0
- data/README.md +244 -0
- data/Rakefile +8 -0
- data/active-query-explorer.gemspec +40 -0
- data/app/assets/javascripts/active_query_explorer/application.js +433 -0
- data/app/assets/stylesheets/active_query_explorer/application.css +562 -0
- data/app/controllers/active_query_explorer/queries_controller.rb +54 -0
- data/app/views/active_query_explorer/queries/index.html.erb +50 -0
- data/config/routes.rb +9 -0
- data/lib/active_query_explorer/engine.rb +31 -0
- data/lib/active_query_explorer/query_discovery.rb +60 -0
- data/lib/active_query_explorer/query_executor.rb +37 -0
- data/lib/active_query_explorer/query_text_formatter.rb +50 -0
- data/lib/active_query_explorer/result_serializer.rb +19 -0
- data/lib/active_query_explorer/version.rb +5 -0
- data/lib/active_query_explorer.rb +17 -0
- metadata +195 -0
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,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
|