Update handoff docs for completed engine layer
Mark engine as done in overview and build order. Update next-steps to focus on test gap analysis. Add engine file map, data flow, and dev setup quirks (Zeitwerk conflict, engine routes conflict) to architecture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,13 +4,28 @@
|
||||
|
||||
```
|
||||
lib/
|
||||
whetrails.rb Public entry point. Exposes Whetrails.inspect_model(ModelClass)
|
||||
whetrails.rb Public entry point. Requires engine if Rails is loaded.
|
||||
whetrails/
|
||||
version.rb VERSION constant (0.1.0)
|
||||
chain.rb Data structures (ModelChain, CallbackEntry, ValidatorEntry)
|
||||
inspector.rb Core engine — reads Rails internals, builds ModelChain
|
||||
formatter.rb Terminal renderer — formats ModelChain as plain text
|
||||
cli.rb CLI logic — boots Rails, resolves model name, calls inspector
|
||||
engine.rb Rails::Engine definition (isolate_namespace Whetrails)
|
||||
engine_routes.rb Engine routes — resources :models + root
|
||||
|
||||
app/
|
||||
controllers/whetrails/
|
||||
application_controller.rb Base: before_action loads all user-defined AR models
|
||||
models_controller.rb index (redirect to first model), show (render chain)
|
||||
helpers/whetrails/
|
||||
models_helper.rb vscode_uri, short_path, condition_label, validator_type_label
|
||||
views/
|
||||
layouts/whetrails/
|
||||
application.html.erb Layout: sidebar model list + main content area
|
||||
whetrails/models/
|
||||
index.html.erb Empty — index action always redirects
|
||||
show.html.erb Lifecycle timeline + validators table
|
||||
|
||||
exe/
|
||||
whetrails Executable entry point — calls Whetrails::CLI.run(ARGV)
|
||||
@@ -22,6 +37,8 @@ app/models/order.rb Development fixture model with sample callbacks/
|
||||
|
||||
## Data flow
|
||||
|
||||
### CLI
|
||||
|
||||
```
|
||||
exe/whetrails
|
||||
→ CLI#run_inspect
|
||||
@@ -40,6 +57,23 @@ exe/whetrails
|
||||
→ Formatter#render prints to stdout
|
||||
```
|
||||
|
||||
### Engine (browser UI)
|
||||
|
||||
```
|
||||
GET /whetrails/
|
||||
→ ModelsController#index
|
||||
→ eager_load! + discover user models via Object.const_source_location
|
||||
→ redirect_to first model
|
||||
|
||||
GET /whetrails/models/:id (:id is the model name, e.g. "Order")
|
||||
→ ModelsController#show
|
||||
→ params[:id].constantize → model class
|
||||
→ Inspector#inspect_model → ModelChain
|
||||
→ renders show.html.erb: lifecycle timeline grouped by event,
|
||||
each callback shows filter name, vscode:// source link, condition badges,
|
||||
module badge if defined in a concern
|
||||
```
|
||||
|
||||
## Key Rails API details
|
||||
|
||||
These are the actual Rails internals the inspector uses — documented here because they are
|
||||
@@ -88,6 +122,34 @@ validator.options[:if] # => conditions (NOT instance variables — different
|
||||
validator.options[:unless]
|
||||
```
|
||||
|
||||
## Dev setup quirks
|
||||
|
||||
This repo is both the gem source and the development Rails app. That creates two issues
|
||||
that were solved and should not be undone:
|
||||
|
||||
**1. Zeitwerk autoloading conflict**
|
||||
|
||||
`config/application.rb` has `config.autoload_lib(ignore: %w[assets tasks whetrails.rb whetrails])`.
|
||||
The `whetrails.rb` and `whetrails` entries are critical: without them, Zeitwerk tries to
|
||||
autoload `lib/whetrails/chain.rb` as `Whetrails::Chain` (wrong constant name), crashing on
|
||||
every request. The gem files use manual `require_relative` — they must not be Zeitwerk-autoloaded.
|
||||
|
||||
**2. Engine routes conflict**
|
||||
|
||||
`lib/whetrails/engine.rb` overrides the engine's routing path:
|
||||
```ruby
|
||||
config.paths.add "config/routes.rb", with: "lib/whetrails/engine_routes.rb"
|
||||
```
|
||||
Without this, Rails uses `config/routes.rb` as the engine's routes file (same directory),
|
||||
which causes `mount Whetrails::Engine` to execute twice and fail with a duplicate route name error.
|
||||
Engine routes live in `lib/whetrails/engine_routes.rb` instead.
|
||||
|
||||
**3. Gem not in Gemfile by default**
|
||||
|
||||
The gem is listed as `gem "whetrails", path: "."` in `Gemfile`. This is intentional —
|
||||
`Bundler.require` must explicitly load the gem so `Whetrails::Engine` is defined before
|
||||
`config/routes.rb` tries to mount it.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **Proc source location**: Proc-based callbacks (e.g. `before_save { do_something }`) report
|
||||
|
||||
@@ -1,64 +1,34 @@
|
||||
# Next Steps
|
||||
|
||||
## Immediate: Rails Engine (browser UI)
|
||||
## Immediate: Test Gap Analysis
|
||||
|
||||
Mount a UI at `/whetrails` inside the host Rails app. A developer adds the gem to their
|
||||
Gemfile, mounts the engine in `config/routes.rb`, starts the server, and visits `/whetrails`.
|
||||
The engine is done. The next layer uses the extracted chain as input to find uncovered
|
||||
callbacks and validators.
|
||||
|
||||
### What the UI needs to show
|
||||
### How it works
|
||||
|
||||
- A list of all models in the app (left sidebar or index page)
|
||||
- Click a model → full chain view (same data as CLI, but visual)
|
||||
- Lifecycle timeline: each stage as a row, callbacks as nodes within it
|
||||
- Source file/line as a clickable link (opens in editor via `vscode://` URI scheme)
|
||||
- Conditions rendered inline (`if: :paid?` shown as a badge)
|
||||
- Callbacks from concerns/gems visually distinguished from model-defined ones
|
||||
|
||||
### Technical approach for the engine
|
||||
|
||||
```ruby
|
||||
# lib/whetrails/engine.rb
|
||||
module Whetrails
|
||||
class Engine < ::Rails::Engine
|
||||
isolate_namespace Whetrails
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Rails engine conventions:
|
||||
- Routes go in `config/routes.rb` inside the gem
|
||||
- Controllers in `app/controllers/whetrails/`
|
||||
- Views in `app/views/whetrails/`
|
||||
- The host app mounts it: `mount Whetrails::Engine, at: "/whetrails"`
|
||||
|
||||
The engine controllers call `Whetrails::Inspector` directly — no duplication of logic.
|
||||
|
||||
### Model discovery
|
||||
|
||||
The engine index page needs to list all AR models in the host app:
|
||||
|
||||
```ruby
|
||||
Rails.application.eager_load!
|
||||
models = ApplicationRecord.descendants # or ActiveRecord::Base.descendants
|
||||
```
|
||||
|
||||
## After engine: Test Gap Analysis
|
||||
|
||||
Once the chain is extracted, gap analysis is:
|
||||
|
||||
1. Run the test suite with SimpleCov (or equivalent) to get line coverage data
|
||||
2. For each callback method in the chain, check whether its source lines are covered
|
||||
3. For each validator, check whether the validation path is exercised
|
||||
1. Run the host app's test suite with SimpleCov to get line coverage data
|
||||
2. For each `CallbackEntry` in the `ModelChain`, check whether `source_file:source_line`
|
||||
is covered in the SimpleCov report
|
||||
3. For each `ValidatorEntry`, check whether the validation path is exercised
|
||||
4. Report: "these 3 callbacks have no test coverage"
|
||||
|
||||
The chain data structure (`ModelChain`) is already the right shape for this — each
|
||||
`CallbackEntry` has `source_file` and `source_line`, which can be cross-referenced
|
||||
against SimpleCov's coverage report.
|
||||
The `ModelChain` data structure is already the right shape — each `CallbackEntry` has
|
||||
`source_file` and `source_line`, ready to cross-reference against SimpleCov output.
|
||||
|
||||
## Product / distribution
|
||||
### Integration point
|
||||
|
||||
- Publish to RubyGems.org once the engine is working
|
||||
- The gem should be dev/test-only (add to Gemfile under `group :development`)
|
||||
Surface this in the engine UI: add a "Coverage" column to each callback row, showing
|
||||
green/red/unknown based on whether SimpleCov data is available and whether the line
|
||||
is covered.
|
||||
|
||||
SimpleCov writes JSON to `coverage/.resultset.json` after a test run. Parse that file
|
||||
directly — no need to instrument the test suite beyond what SimpleCov already does.
|
||||
|
||||
## After gap analysis: distribution
|
||||
|
||||
- Publish to RubyGems.org
|
||||
- The gem should be dev/test-only (add under `group :development`)
|
||||
- Pricing model TBD — could be open source with a paid cloud version, or MIT with
|
||||
a commercial license for teams
|
||||
|
||||
@@ -70,3 +40,5 @@ against SimpleCov's coverage report.
|
||||
- Dev database: SQLite at `storage/development.sqlite3`
|
||||
- Gitea running in Docker on localhost:3000 (container name: `gitea`)
|
||||
- Gitea API token for `bill`: `c2948775b80bcaa0ba65c6359658d0d48c340c4e`
|
||||
- Dev server runs on port 3100 (`bin/rails server -p 3100`)
|
||||
- Visit `http://localhost:3100/whetrails` to see the engine UI
|
||||
|
||||
@@ -10,20 +10,19 @@ This is the first half of a two-part tool. The second half (not yet built) uses
|
||||
|
||||
## Current state (as of 2026-06-08)
|
||||
|
||||
Two layers are complete:
|
||||
Three layers are complete:
|
||||
|
||||
1. **Core inspector** — extracts the full chain from any ActiveRecord model class at runtime
|
||||
2. **CLI** — `whetrails inspect <ModelName>` boots the host Rails app and renders the chain to the terminal
|
||||
|
||||
The next layer (Rails engine / browser UI) has not been started.
|
||||
3. **Rails engine** — browser UI mounted at `/whetrails`, sidebar model list, lifecycle timeline with vscode source links
|
||||
|
||||
## Build order
|
||||
|
||||
```
|
||||
[DONE] 1. Gem core lib/whetrails/inspector.rb
|
||||
[DONE] 2. CLI exe/whetrails + lib/whetrails/cli.rb
|
||||
[NEXT] 3. Rails engine browser UI mounted at /whetrails
|
||||
4. Test gap layer uses chain data to find uncovered callbacks/validators
|
||||
[DONE] 3. Rails engine browser UI mounted at /whetrails
|
||||
[NEXT] 4. Test gap layer uses chain data to find uncovered callbacks/validators
|
||||
```
|
||||
|
||||
## Repository
|
||||
|
||||
Reference in New Issue
Block a user