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 runner or 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

License

MIT