Add handoff documentation
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

Overview, architecture notes, and next steps for anyone
picking this up — including Rails API gotchas discovered
during implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 10:27:39 -06:00
parent df3f7e1c75
commit 6dd78f2487
3 changed files with 209 additions and 0 deletions

105
handoff/architecture.md Normal file
View File

@@ -0,0 +1,105 @@
# Architecture
## File map
```
lib/
whetrails.rb Public entry point. Exposes Whetrails.inspect_model(ModelClass)
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
exe/
whetrails Executable entry point — calls Whetrails::CLI.run(ARGV)
whetrails.gemspec Gem metadata and dependencies
app/models/order.rb Development fixture model with sample callbacks/validators
```
## Data flow
```
exe/whetrails
→ CLI#run_inspect
→ CLI#boot_rails loads the host app's config/application.rb
→ CLI#resolve_model turns "Order" string into Order class constant
→ Inspector#call
reads Model._validation_callbacks (before/after_validation)
reads Model._save_callbacks (before/after_save)
reads Model._create_callbacks (before/after_create)
reads Model._update_callbacks (before/after_update)
reads Model._destroy_callbacks (before/after_destroy)
reads Model._commit_callbacks (after_commit)
reads Model._rollback_callbacks (after_rollback)
reads Model.validators (all validators)
→ returns ModelChain
→ Formatter#render prints to stdout
```
## Key Rails API details
These are the actual Rails internals the inspector uses — documented here because they are
undocumented in official Rails docs and took investigation to discover.
### Callbacks
Each `_<event>_callbacks` method returns an `ActiveSupport::Callbacks::CallbackChain`.
Each entry in the chain is an `ActiveSupport::Callbacks::Callback` with:
| Field | Access | Notes |
|---|---|---|
| Lifecycle stage | `cb.kind` | `:before`, `:after`, or `:around` |
| Method name | `cb.filter` | Symbol (method name) or Proc |
| `if:` conditions | `cb.instance_variable_get(:@if)` | Array — `.options` does NOT exist |
| `unless:` conditions | `cb.instance_variable_get(:@unless)` | Array |
`cb.options` does **not** exist in Rails 8 — this is a common mistake from outdated docs.
Conditions are stored in `@if` / `@unless` instance variables.
### Source location
```ruby
# For symbol callbacks:
model.instance_method(cb.filter).source_location # => ["path/to/file.rb", 42]
model.instance_method(cb.filter).owner # => the module that defines it
# For proc callbacks:
cb.filter.source_location
```
### Rails internal filter
Two types of internals are filtered out:
1. Callbacks whose source file is inside a Rails gem (`/gems/activerecord-*` or `/gems/activemodel-*`)
2. Conditions that are `ActiveSupport::Callbacks::Conditionals::*` instances (Rails adds these
internally to `after_create` / `after_update` to guard against wrong lifecycle stage)
### Validators
```ruby
Model.validators # => Array of validator objects
validator.class # => ActiveRecord::Validations::PresenceValidator, etc.
validator.attributes # => [:status]
validator.options[:if] # => conditions (NOT instance variables — different from callbacks)
validator.options[:unless]
```
## Known limitations
- **Proc source location**: Proc-based callbacks (e.g. `before_save { do_something }`) report
source location from Ruby's Proc#source_location, which may point inside a concern or DSL
wrapper rather than the user's code. This is a Ruby limitation.
- **`_callbacks` vs `__callbacks`**: The research phase incorrectly identified `_callbacks`
as the API. The actual method is `__callbacks` (two underscores) for the hash, or individual
chain accessors like `_save_callbacks`. The individual accessors are used in this codebase.
- **Validator source location**: Not yet implemented. Validators don't expose source location
directly. A future approach would be to patch `validate` / `validates` at load time to capture it.
- **around callbacks**: Registered but the lifecycle semantics are more complex (they wrap the
operation with yield). Not currently rendered differently from before/after.

72
handoff/next-steps.md Normal file
View File

@@ -0,0 +1,72 @@
# Next Steps
## Immediate: Rails Engine (browser UI)
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`.
### What the UI needs to show
- 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
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.
## Product / distribution
- Publish to RubyGems.org once the engine is working
- The gem should be dev/test-only (add to Gemfile under `group :development`)
- Pricing model TBD — could be open source with a paid cloud version, or MIT with
a commercial license for teams
## Environment notes
- Ruby 3.3.6 installed via rbenv (`~/.rbenv/versions/3.3.6`)
- rbenv init added to `~/.zshrc`
- Rails 8.1.3
- Dev database: SQLite at `storage/development.sqlite3`
- Gitea running in Docker on localhost:3000 (container name: `gitea`)
- Gitea API token for `bill`: `c2948775b80bcaa0ba65c6359658d0d48c340c4e`

32
handoff/overview.md Normal file
View File

@@ -0,0 +1,32 @@
# Whetrails — Project Handoff
## What this is
A Ruby gem that makes ActiveRecord callback and validation chains visible to developers.
When a Rails developer calls `.save` on a model, Rails fires a chain of callbacks and validations in a specific order. These are registered in scattered places (the model file, concerns, gems) and Rails never surfaces the full picture in one place. Whetrails extracts and displays that chain: what fires, in what order, where it's defined, and under what conditions.
This is the first half of a two-part tool. The second half (not yet built) uses the extracted chain as input to a test coverage gap analyzer — showing which callbacks and validators are never exercised by the test suite.
## Current state (as of 2026-06-08)
Two 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.
## 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
```
## Repository
- Gitea: http://localhost:3000/bill/whetrails
- Branch: main