Refactoring a mature Rails app often starts with a simple question:
If I change this method, what else could break?
In a smaller codebase, grep might be enough.
In a larger Rails app, not really.
A method can be connected through:
- callbacks
- associations
- routes
- background jobs
- mailers
- concerns
- templates
- tests
- indirect Ruby callers
And because Ruby is dynamic, even resolving something that looks like a simple method call can become ambiguous.
That made change-impact analysis an interesting problem to work on.
The rule I chose early
I'd rather miss an ambiguous relationship than confidently invent one.
So instead of pretending every relationship is certain, the analyzer records evidence and confidence around what it finds.
For example:
ripple-effect inspect 'BillingService#charge'
The goal is to answer questions like:
- Who calls this?
- Which Rails components depend on it?
- What can reach it indirectly?
- Which tests are likely relevant?
- Why does the analyzer think this relationship exists?
- Where is it unsure?
You can also inspect the impact of a branch:
ripple-effect diff main
The result became RippleEffect
RippleEffect is an open-source Ruby gem for static change-impact analysis in Rails apps.
It does not:
- boot the Rails app
- connect to the database
- evaluate application code
- upload source
- call an LLM
Everything is analyzed locally and deterministically.
I've been testing the first release against real Rails codebases including Lobsters, RubyGems.org, Solidus, and Mastodon.
There are still plenty of hard cases in dynamic Ruby, which is exactly what makes the problem interesting.
If you work on large Rails apps, I'd be curious:
How do you currently answer "what else could this change affect?" before a refactor?
GitHub: https://github.com/iamzayn19/ripple-effect
RubyGems: https://rubygems.org/gems/ripple_effect

Top comments (2)
I agree that Rails' dynamic nature makes static impact analysis tricky; in my work I've found that combining type‑checking gems like Sorbet with a focused test suite can surface hidden dependencies early. We also instrument method call graphs at runtime to generate a cheap impact matrix before refactors. Have you tried generating such call graphs, and what was the biggest blind spot you hit?
Good point, Sorbet can definitely help reduce some ambiguity. I’ve kept RippleEffect static-only for now, so the biggest blind spots are metaprogramming-heavy calls, dynamic dispatch, and runtime-generated relationships.
Runtime call graphs are interesting though. I’ve been thinking about whether combining static evidence with optional runtime traces could improve confidence without losing determinism.