The View Layer Rails Couldn’t See | Davidslv
Skip to content
The Engineer's Notebook
Why Architecture Matters in Rails Applications<br>How to Identify Boundaries in a Rails Monolith<br>The Modular Monolith as the Default Starting Point<br>Rails Engines vs Packwerk: When to Use What<br>When Rails Engines Are the Wrong Tool<br>Testing Strategy for a Modular Rails Application<br>From One Controller to Thirteen Handlers: A Webhook Refactor<br>The Propshaft Version Lever You Were Told Was Gone<br>The View Layer Rails Couldn't See
← All writing
On this page
The Rails view-layer question has consolidated. After years of churn — Arbre, Erector, the Slim and Haml DSLs, then the components debate — the community has settled, and at the same time ERB itself is being rebuilt as a first-class, HTML-aware foundation. The interesting part is not which template engine won the argument. It is why it won, and what changed underneath it to make the win durable. The community is moving toward ERB, not away from it, and the reason is a tool called Herb.
I want to be specific about this, because I have been living it. We run Marco Roth’s Herb toolchain in production in a Rails engine, and the benefits were not the ones I expected when I wired it in. The headline feature — “an HTML-aware ERB parser” — sounds like a linter upgrade. It is actually the closing of a blind spot that Rails has had for its entire existence.
The blind spot
Think about where static analysis already reaches in a mature Rails app. Your Ruby is read by RuboCop, line by line, with an AST that understands the language. Your security surface is read by Brakeman, which traces taint from request to query. Your migrations get checked for lock risk. Your JavaScript has ESLint and a type checker. Every layer of the stack has a tool that understands it — that parses it into a structure and reasons about that structure.
Except one. The layer that actually emits HTML to the user.
For twenty years, ERB tooling was string-based. The linters of the previous generation — erb_lint, better-html — did real and useful work, but they were fundamentally pattern-matching over text with some clever heuristics bolted on. They did not have a parse tree of the HTML you were producing. They could not reliably tell you that you had opened a inside a , or that you had interpolated user data into an attribute position where escaping rules differ from body text, because they were not parsing HTML — they were parsing ERB and squinting at the strings between the tags.
And that is the good case. The worse case is the one we actually had: large parts of our admin surface were written in Arbre , the Ruby DSL that ActiveAdmin uses to build markup with div do … end blocks instead of templates. Arbre is elegant to write. It is also completely invisible to every modern static-analysis tool. No HTML linter sees it, because there is no HTML in the file — only Ruby that happens to produce HTML at runtime. No language server can tell you a Stimulus target is misspelled inside it. No formatter touches it. Every .html.arb file we wrote was, in a real and measurable sense, a file outside the ecosystem.
What “HTML-aware” actually buys you
Herb is written in C and parses HTML-with-embedded-ERB into a genuine syntax tree. That single architectural decision is the whole story, because it moves an entire class of checks from “impossible with regexes” to “trivial with a tree.”
In our engine, herb analyze runs a set of parser-level validators that the old string linters simply could not express:
Security — ERB output in attribute position, where the escaping context is different from body text and where a naive can let an attacker break out of an attribute. You cannot find this reliably without knowing you are in an attribute, which means you need to have parsed the HTML.
Nesting — malformed or invalid element nesting, the kind that browsers paper over silently and then render differently than you intended.
Accessibility — missing labels, the structural a11y checks that only make sense once you have the element tree in front of you.
None of those are exotic. They are the bread-and-butter mistakes that ship to production because nothing in the pipeline was looking. The shift from a string linter to a tree-based one is the same shift Ruby made when it got a real parser: you stop catching some of a problem with heuristics and start catching the actual problem structurally.
Read the room: where the view layer has actually been heading
This is not one tool appearing in a vacuum. It is the latest move in a decade-long consolidation, and the direction matters more than any single release. Here is the throughline:
2019<br>ViewComponent born at GitHub
An ERB template plus a Ruby class. Built and run by the most prolific ERB renderer on the planet — and still rendering ERB.
2023 · Rails 7.1<br>Strict locals land
A magic comment gives plain partials required, typed parameters — closing much of the safety gap that sent...