activerecord_callback_lens
X-ray your ActiveRecord callbacks.
Callbacks are easy to add and hard to reason about. activerecord_callback_lens statically analyzes the callbacks registered on your ActiveRecord models, parses their if/unless conditions into logical trees, and renders the result as a Mermaid diagram, Graphviz DOT graph, or a self-contained HTML report so you can see exactly what runs and why.
Requirements
- Ruby >= 3.2
- ActiveRecord >= 7.0, < 9.0
Installation
Add to your Gemfile:
gem "activerecord_callback_lens"
Or install directly:
gem install activerecord_callback_lens
Usage
CLI
Point the callback_lens binary at any loaded ActiveRecord model:
callback_lens analyze User
This prints a Mermaid graph TD diagram to stdout showing every callback and its condition dependencies.
graph TD
n0["before_save"]
n1["AndNode"]
n2["saved_change_to_title?"]
n3["status_completed?"]
n2 --> n1
n3 --> n1
n1 --> n0
Note: The model class must be loaded before analysis. In a Rails app, run the binary inside
rails runneror require your environment first.
Rake task (Rails)
The gem ships a Railtie that automatically loads the callback_lens namespace into your Rails rake tasks:
rake callback_lens:analyze MODEL=User
rake callback_lens:mermaid MODEL=User # alias
Recursive method expansion
Pass --expand (CLI) or EXPAND=true (Rake) to recursively resolve symbol
callback conditions into their ConditionTree representations (up to 5
levels deep). Cycles and unresolvable methods are left unexpanded with a
warning printed to stderr.
# CLI
callback_lens analyze User --expand --mermaid
# Rake
rake callback_lens:analyze MODEL=User EXPAND=true
rake callback_lens:mermaid MODEL=User EXPAND=true
Only the exact value EXPAND=true (case-insensitive) enables expansion; any
other value (EXPAND=1, EXPAND=yes, empty, or absent) leaves it off.
Graphviz DOT output
Pass --graphviz (CLI) or use callback_lens:graphviz (Rake) to output a
Graphviz DOT diagram to stdout. Pipe it to dot to
generate an image:
# CLI — print DOT to stdout
callback_lens analyze User --graphviz
# CLI — combine with Mermaid output
callback_lens analyze User --mermaid --graphviz
# CLI — pipe to dot for a PNG
callback_lens analyze User --graphviz | dot -Tpng -o callbacks.png
# Rake
rake callback_lens:graphviz MODEL=User
rake callback_lens:graphviz MODEL=User EXPAND=true
Requires Graphviz only when piping to dot.
HTML report
Pass --html FILE (CLI) or use callback_lens:html (Rake) to generate a
self-contained HTML report. The file embeds a Mermaid diagram (via CDN), an
inline Graphviz SVG (when dot is installed), a callback list table, an
execution-order flow, and a nested dependency tree for every condition — no
external dependencies at view time.
# CLI — write report to a file
callback_lens analyze User --html report.html
# CLI — combine with stdout output
callback_lens analyze User --mermaid --html report.html
# Rake — default output filename: callback_lens_report.html
rake callback_lens:html MODEL=User
# Rake — custom output path
rake callback_lens:html MODEL=User OUT=tmp/user_callbacks.html
# Rake — with recursive method expansion
rake callback_lens:html MODEL=User OUT=tmp/user_callbacks.html EXPAND=true
| ENV variable | Required | Default | Description |
|---|---|---|---|
MODEL |
Yes | — | ActiveRecord model class name |
OUT |
No | callback_lens_report.html |
Output path for the HTML file |
EXPAND |
No | false |
Set to true to recursively expand method conditions |
Programmatic API
require "activerecord_callback_lens"
# 1. Collect raw callback definitions
definitions = ActiverecordCallbackLens::Collector::CallbackCollector.collect(User)
# 2. Parse if/unless conditions into ConditionTrees
definitions = definitions.map { |d| ActiverecordCallbackLens::Parser::ConditionParser.parse(d) }
# 3. Build a dependency graph
graph = ActiverecordCallbackLens::Graph::GraphBuilder.build(definitions)
# 4. Render as Mermaid
puts ActiverecordCallbackLens::Renderer::MermaidRenderer.render(graph)
Example app
A runnable example Rails app lives in examples/blog_app/.
It defines a small blog domain (Article, User, Comment) with callbacks
covering every lifecycle event and condition style, and points its Gemfile at
this repo via path: "../.." so it always exercises the current gem code:
cd examples/blog_app
bundle install
bin/rails callback_lens:analyze MODEL=Article # Mermaid diagram
bin/rails callback_lens:analyze MODEL=Article EXPAND=true # expanded predicates
bin/rails callback_lens:html MODEL=Article OUT=reports/article.html
bin/callback_lens_demo # programmatic API demo
See examples/blog_app/README.md for the full
command matrix and expected output.
How it works
| Layer | Class | Responsibility |
|---|---|---|
| Collector | CallbackCollector |
Reads ActiveRecord's internal _save_callbacks, _create_callbacks, _update_callbacks, _destroy_callbacks, and _validation_callbacks chains |
| Parser | ConditionParser |
Uses Prism to parse Proc/Lambda conditions into AndNode/OrNode/NotNode/PredicateNode trees; Symbol conditions become MethodRefNode stubs |
| Resolver | MethodResolver |
Recursively expands MethodRefNode symbols into full ConditionTree sub-trees (up to 5 levels deep) with cycle detection |
| Graph | GraphBuilder |
Assembles a DAG of CallbackNode, ConditionNode, PredicateNode, and MethodNode values |
| Analyzer | ExecutionOrderAnalyzer |
Sorts definitions into canonical Rails execution order (create or update path) |
| Renderer | MermaidRenderer |
Serializes the graph to a Mermaid graph TD string |
| Renderer | GraphvizRenderer |
Serializes the graph to a Graphviz DOT string; #to_svg shells out to dot |
| Renderer | HtmlRenderer |
Produces a self-contained HTML report embedding Mermaid, SVG, callback table, execution flow, and dependency tree |
Condition tree nodes
| Node | Meaning |
|---|---|
AndNode |
All children must be true |
OrNode |
At least one child must be true |
NotNode |
Negates its child (from unless:) |
PredicateNode |
A leaf predicate call (e.g. saved_change_to_title?) |
MethodRefNode |
A Symbol condition (e.g. :sync_required?) — expanded in v0.2 |
Roadmap
| Version | Feature |
|---|---|
| v0.1 | CallbackCollector, Prism condition parser, Mermaid renderer, CLI, Rake task |
| v0.2 | MethodResolver — recursive expansion of Symbol conditions (--expand / EXPAND=true) |
| v0.3 | Graphviz / DOT renderer (--graphviz / callback_lens:graphviz) |
| v0.4 | HTML report — callback list table, execution-order flow, dependency tree, embedded Mermaid and Graphviz SVG (--html / callback_lens:html) |
| v1.0 | Runtime tracer via ActiveSupport::Notifications |
| v2.0 | RBS analysis, cross-model dependency graph |