Files
whetrails/handoff/architecture.md
bill 6dd78f2487
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
Add handoff documentation
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>
2026-06-08 10:27:39 -06:00

4.3 KiB

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

# 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

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.