Say No To Partials And Helpers For A Maintainable Rails Front-End

Adam McCrea headshot

Adam McCrea

@adamlogic

I just read Garrett Dimon’s fantastic article “Structure Your ERb and Partials for more Maintainable Front-end Code in Rails”. It’s a wonderfully thorough discussion of how to use partials and helpers effectively. You should read it. I agree with everything he said. And yet here I am writing a rebuttal.

No, “rebuttal” isn’t right, because I don’t disagree with Garrett at all. If you and your team are stuck with the canonical Rails front-end toolkit of ERB partials and helpers, you should absolutely follow Garrett’s advice. But I want to offer a different answer, and that’s to throw out ERB partials and helpers altogether, and use Phlex instead.

I recognize this answer will be extreme for many developers and teams. Many of you feel burned by HAML. Many of you think your designers can only work with HTML (or are only willing to work with HTML??). Many of you want to stick with Omakase Rails at all costs.

That’s fine. Phlex isn’t for everyone. My intention here is just to show you how Phlex addresses all of the concerns from Garrett’s post. Phlex doesn’t so much as solve those problems as much as it makes the questions go away entirely.

When should we use a partial or a helper–or a partial rendered using a helper? Which elements should be extracted into partials or helpers? When can we use a simple helper and skip the partial file? When should we use the built-in tag helpers instead of writing HTML directly?

These questions all assume you have multiple tools for the job of extracting reusable or otherwise self-contained markup—something you might call a “component”. With Phlex, you just extract a component and write your HTML in Ruby. No questions, no trade-offs.

No more trade-offs

Garrett’s article discussed many ways of approaching a simple figure element, and the trade-offs inherent in each of them. We’re not going to do that here. When you write components with Phlex, you don’t have to decide what goes in a partial and what goes in a helper. It all goes in the component, and it’s all Ruby.

Let’s jump right to the finished component:

class Figure < ApplicationComponent
  def initialize(src:, alt:, caption: nil)
    @src = src
    @alt = alt
    @caption = caption
  end

  def view_template(&block)
    @caption = block.call if block

    figure do
      img(src: @src, alt: @alt)
      figcaption { @caption } if @caption.present?
    end
  end
end

And here’s how you’d use it form an ERB view. (Of course you could render it from a Phlex view, but I want to show how nicely the two can coexist.)

<%= render Figure.new(src: "path/to/image.jpg", alt: "Alternate Text") do %>
  <i>Italicized</i> Figure Caption
<% end %>

When you decide to embrace Phlex for your components, all the “maintainability” questions with partials and helpers just fall away. You’re just writing Ruby.

Explicit Ruby without hacks

The beauty of Phlex is that it defines the contract and dependencies of a component in plain Ruby. Our Figure component has two required named attributes:

def initialize(src:, alt:, caption: nil)

Compare this to the first line of the ERB partial:

<%# locals: (src:, alt:, figcaption: nil) -%>

One of these is basic Ruby syntax, and one is an optional magic comment introduced by Rails 7.1. I know which one I prefer.

Seeing the shape of the markup

You might notice that the Phlex implementation looks at lot like Garrett’s helper implementation that uses the tag method:

def figure(src, alt:, figcaption: nil)
  figure_content = tag.img(src: src, alt: alt)
  figure_content += tag.figcaption(figcaption) if figcaption.present?

  tag.figure do
    figure_content
  end
end

One of his concerns here is regarding “shape” and “structure”:

It generates all of the markup via tag helpers, and that obfuscates the structure of the underlying markup. It becomes more difficult to see the shape, and it would be more challenging for a purely front-end developer to make changes without help from a back-end developer.

I argue that Phlex solves that by encouraging you to shape your Ruby just as you would your HTML—after all, the structure of HTML elements and attributes very closely mirrors the structure of Ruby blocks and keyword parameters. Most developers and designers alike would have no trouble seeing the HTML structure in this code:

figure do
  img(src: @src, alt: @alt)
  figcaption { @caption } if @caption.present?
end

Beyond a simple example

Of course the figure example was kept simple for the purpose of a blog post. It had to be that way. But Phlex really starts to shine when you have more complex components. The reason is simple: it’s just a Ruby class.

As you start to build out a complex Phlex component, you’ll naturally begin refactoring parts of it into well-named private methods. You’ll extract reused strings like Tailwind classes into local variables and constants. You’ll use all the tools that come naturally to you as a Ruby developer.

These tools don’t exist in ERB partials, and they barely exist in helpers. What you usually end up with is a scattering of partials and helper methods, related in name but defined globally. Nothing is really self-contained, and you’re bouncing between files to try and follow along.

But HTML is wonderful!!

Garrett ends with this sentiment:

Context matters both in the code and on the team creating the code.

I couldn’t agree more. If you or your team are HTML/ERB purists, this approach certainly isn’t going to fly.

But if you work solo or if you’re on a small team, I encourage you to give it a try. I don’t hate HTML, but I’ve worked on enough Rails apps to know that the view layer is always the messiest. That’s not because of HTML—it’s because of ERB partials and helpers, tools that fall way short in terms of readability and refactorability.

When you embrace Phlex as a replacement for partials and helpers, you’ll inevitably want to extract more components. Your components will be easier to read, easier to maintain, and you’ll find yourself wanting to write front-end code again. Long live the view layer!