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 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.
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)
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 |
| Graph | GraphBuilder |
Assembles a DAG of CallbackNode, ConditionNode, PredicateNode, and MethodNode values |
| Renderer | MermaidRenderer |
Serializes the graph to a Mermaid graph TD string |
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 |
| v0.4 | HTML report (callback list, execution flow, embedded diagram) |
| v1.0 | Runtime tracer via ActiveSupport::Notifications |
| v2.0 | RBS analysis, cross-model dependency graph |