Table of Contents
- The Original Static HTML
- Options with Our Partial
- Rendering Inline vs. Calling a Helper
- On Finding Balance
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 are all nuanced questions without objective right or wrong answers. So we’ll explore options in the context of a real example and take a holistic approach to translating static markup into a partial and the relevant helper methods. We’ll dig into the trade-offs and alternatives that inform and explain the decision-making.
With any partial, we want to make sure that it’s both easy to use and maintain. We want minimal friction for using frequently referenced partials or helpers, but we also want some amount flexibility in how they’re used.
This is the challenge.
If it’s too complex, we won’t use it because it will be too difficult to remember the precise syntax.
If it’s too rigid, we won’t use it as frequently because it won’t be versatile enough to be useful in multiple scenarios.
Let’s walk through converting static HTML to a partial that we can both use and maintain for years to come by finding the right balance of complexity and flexibility.
The Original Static HTML
For our example, we’ll look at how we could organize the code related to a figure element (Figure 1)
. It offers just enough complexity to illustrate how we could structure it without being so complex that we get caught up in the weeds.
<figure> <img src="/path/to/image.jpg" alt="Alternate Text" /> <figcaption>Figure Caption</figcaption></figure> With this markup for a figure, we’re able to illustrate a handful of considerations and techniques for working with front-end code in Rails in a maintainable manner.
↩︎For some initial observations, this is a great candidate for a partial, but it’s also simple enough that we could make a case for it to be handled entirely within a helper method. We’ll look at both approaches, but we’ll gravitate towards the partial approach for demonstration purposes.
- It’s a good candidate for a partial (or helper) because it’s fully-contained within a single
figureelement making it nicely atomic. - It has two required parameters in the form of
srcandaltattributes. Both of these should only ever be strings. - It has a an optional
figcaptionelement that supports “Flow Content”–meaning its content can use HTML to provide richer text.
Let’s start by looking at a few options for how we could convert this static HTML into a dynamic partial.
Options with Our Partial
We’ll start by creating our partial (app/views/shared/_figure.html.erb), and we’ll see several approaches for how we could write it.
All-ERb
We could define the partial entirely with ERb (Figure 2)
, but unless the figure or figcaption elements have some dynamic attributes, using a tag helper is unnecessary.
<%# locals: (src:, alt:, figcaption: nil) -%><%= tag.figure do %> <%= tag.img src: src, alt: alt %> <%= tag.figcaption do %> <%= figcaption %> <% end if figcaption.present? -%><% end %> This approach will get the job done, but both the figure and figcaption elements would likely be better off as plain HTML.
While there’s nothing technically wrong here, the partial leans heavily towards back-end code and becomes slightly more challenging to maintain unless everyone on the team is comfortable with Ruby and ERb.
Moreover, if a partial is entirely made of ERb, there’s not much value in having a dedicated partial for it. Instead, the code would likely be easier to follow as a pure helper method. (We’ll look at an example of that later.)
HTML-centric ERb
Our next option strives to keep as much static HTML as possible. This reduces the chances of anything being lost in translation between tag helpers and their rendered markup, but it also means more dancing in and out of ERb.
Generally speaking, partials work best when they’re treated as HTML-first and only add minimal ERb for the dynamic elements. Since the goal of an HTML partial is to render markup, we should avoid the indirection of tag helpers unless they’re generating dynamic elements or attributes.
Let’s look at an HTML-centric approach where we leave as much static HTML as possible. (Figure 3) It’s incredibly close to the original HTML, and it’s very easy to follow.
<%# locals: (src:, alt:, figcaption: nil) -%><figure> <img src="<%= src %>" alt="<%= alt %>" /> <% if figcaption.present? -%> <figcaption><%= figcaption %></figcaption> <% end -%></figure> In this context, we have a partial that’s more likely to be editable by someone who is familiar with HTML but not familiar with ERb. It reads almost like pure HTML.
↩︎There’s certainly a reduction in indirection here, but it involves much more jumping an and out of ERb expressions. It’s certainly more readable for a front-end developer, but it loses out on the potential flexibility of using tag helpers for the dynamic pieces.
With a smaller partial like this, it doesn’t feel egregious, but for a larger partial with more moving parts, it can quickly become challenging to dis-entangle everything that’s happening. We could definitely make this more atomic.
Element-centric ERb
Since both attributes of the img tag are dynamic, we could consolidate those two ERb expressions into a single tag.img helper call. This reduces the degree to which we’re switching in and out of ERb, but it would require everyone on the team to be comfortable with the tag helpers.
<%# locals: (src:, alt:, figcaption: nil) -%><figure> <%= tag.img src: src, alt: alt %> <% if figcaption.present? -%> <figcaption><%= figcaption %></figcaption> <% end -%></figure> Since our figure and figcaption elements don’t rely on any dynamic values for their attributes, we can use static HTML. Since the figcaption is optional, however, we’ll need to add a conditional around it.
Generally speaking, if an HTML element has dynamic content but no dynamic attributes (like figcaption in the example), I tend to prefer leaving the static HTML elements in the partial.
When the element is optional, however, we don’t want empty HTML elements to exist in the DOM, so it would be nice if the three conditional lines for the figcaption could be represented in a single expression so it feels more atomic.
ERb-centric Atomic Elements
For a sort of happy medium, we can convert the figcaption from static HTML to a helper with a trailing conditional. This makes it less-friendly for front-end developers who aren’t as knowledgeable of ERB, but for many teams, that’s not a problem.
<%# locals: (src:, alt:, figcaption: nil) -%><figure> <%= tag.img src: src, alt: alt %> <%= tag.figcaption { figcaption } if figcaption.present? -%></figure> Since our figcaption has dynamic content and may or may not be present, we can make it more atomic by treating it as a single ERb expression.
Assuming someone is comfortable with both ERb and HTML, this approach feels like the most natural and intuitive because it makes it very clear which elements are dynamic and which are static. Even better, each element is entirely contained within a single HTML element or a single ERb expression.
Rendering Inline vs. Calling a Helper
Assuming we’ve chosen to go with a partial rather than a helper, we’re looking at having to call render every time we want to use this partial. As the examples show, these calls aren’t horribly complex or unusual, but they’re hardly as convenient as they could be. (Figure 6)
<%# Abbreviated Render Call %><%= render "shared/figure", src: src, alt: alt, figcaption: figcaption %><%# Explicit Partial Render Call %><%= render partial: "shared/figure", locals: { src: src, alt: alt, figcaption: figcaption } %> Calling render is hardly a nightmare, but with frequent reuse, we could certainly reduce the friction of using our partial.
Considering that our generic figure partial is likely to be reused frequently throughout a site, we want it to be quick and easy to use. If we wrapped the render calls in helper methods, we could streamline the process of rendering a figure in our ERb.
<%# The absolute minimal usage -%><%= figure "/path/to/image.jpg",alt: "Alternate Text" %><%# Minimal but with a caption -%><%= figure "/path/to/image.jpg", alt: "Alternate Text", caption: "Figure Caption" %> We’re not only reducing the amount of typing, but we’re making it more readable because the first element of the ERb declares exactly what will be rendered. Easier to write. Easier to read.
↩︎While the helper can save some keystrokes, the real win with this approach is improved readability. First, it’s immediately clear that the expression will render a figure because it’s the first word we see. (Figure 7)
It also reduces the number of unnecessarily distracting characters in the calls to render.
The only notable downside is that the helper adds a layer of indirection between the developer and partial. If, however, that indirection helps improve the readability while reducing typing, it feels worthwhile.
We’ve streamlined the call to the helper, but our figcaption supports HTML. It would be a touch more handy to write HTML directly in the template instead of passing an HTML string. So next we’ll make sure our helper better supports HTML content by accepting a block for the figcaption.
Trade-offs with Helpers vs. Rendering Inline
Assuming we expect our partial to be used frequently enough to justify our helper method, let’s see what the helper method looks like. (Figure 8)
module FigureHelper# ...def figure(src, alt:, caption: nil) locals = { src:, alt:, figcaption: caption } render partial: "shared/figure", locals: localsend# ...end Is such a simple helper method worth the additional obfuscation? That depends entirely on the frequency and context of usage.
↩︎On one hand, this feels simple enough that it’s not too worrisome, but on the other hand, if it is so simple, is the helper method even worth it?
Using helpers to wrap partials does add an extra layer of obfuscation. In cases where calls to the figure helper are littered throughout a codebase, it’s likely worth it through reduced typing and improved readability, but if the partial is only ever used a few times in layouts, the convenience and readability probably isn’t worth it.
With partials and helpers, however, we have one more consideration that begins to make a slightly stronger case for adding the helper method to the mix.
Helpers provide a better home for complex logic when compared to performing that same logic within the partial using ERb.
Minimizing Control Structures in Partials
The maintainability of a partial–or any code for that matter–is inversely related to the number of conditionals and control structures. For example, a single if/else statement creates two variations of a partial. If we add another, we now have four (2 _ 2) variations. If we add a third, we now have eight (2 _ 2 * 2) combinations that need to be tested.
In some cases, I’ll consider a second control structure if a good case can be made, but if a partial involves more than two, it starts to raise a red flag. Too many conditionals can make the partial hard to reason about and more difficult to test due to the increasing number of variations it can have.
While we can perform calculations and handle other types of logic directly within the partial, I’ve found that wrapping partials in helpers provides a better home for more complex logic. When the helper handles the logic, the partial can stay more focused on the content rendered. (Figure 9)
module FigureHelper # ... def figure(src, alt:, caption: nil, &block_caption) # Content logic reads better from within the helper caption ||= capture(&block_caption) # Visibly change the output if the alternate text if it's # likely too short to be useful if alt.size < 20 caption = tag.strong("The alternate text is very weak!".upcase) end # We can use `caption` as the keyword for the helper but # then translate it to `figcaption` in the locals for # improved readability in the partial file locals = { src:, alt:, figcaption: caption } render partial: "shared/figure", locals: locals end# ...end In an ideal scenario, partials would be fairly atomic and involve minimal conditionals or control structures. When we use a helper to wrap the partial, we can offload additional logic into the helper where it can be more readable and less entangled with the desired markup.
↩︎As far as helper methods go, this falls in line with how I like to use them to do one very specific thing. They can also help keep conditional logic outside of the partial itself by seamlessly handling default values or making necessary adjustments to the values passed to the partials.
Flexible Arguments with Helpers and Blocks
The figcaption element supports “Flow Content” which means we can include content that uses HTML. If we wanted a figure caption with italicized words, we could use "<i>italics</i>".html_safe. But putting that into a Ruby string gets clunky.
It would be much nicer to write HTML directly from where we’re calling our helper. (And it will often read more cleanly with syntax highlighting in our text editor as well.) What if, instead of passing a keyword argument, we could pass a block that supports HTML right in the ERb? (Figure 10)
<%= figure "/path/to/image.jpg", alt: "Alternate Text" do %> <i>Italicized</i> Figure Caption<% end %> Supporting the figure caption as a block makes it easier for us to write marked-up figure captions.
↩︎In some ways, this is an improvement, but in cases where a figure caption is brief and does not need markup, the do/end block feels kind of clunky relative to using a caption keyword parameter.
So let’s support both!
For that, we’ll definitely want to handle the logic within a dedicated helper for rendering the partial, and we’ll make sure the figcaption content can be plain text or use HTML by accepting a block. (Figure 11)
<%# Passing a string with HTML via keyword parameter %><%= figure "/path/to/image.jpg",alt: "Alternate Text"caption: "<i>Italicized</i> Figure Caption".html_safe %><%# Passing a string with HTML via block %><%= figure "/path/to/image.jpg", alt: "Alternate Text" do %> <i>Italicized</i> Figure Caption<% end %> By supporting a plain-text caption using a keyword argument but also supporting passing that content via a block, we can create a figure partial that’s extremely convenient for plain-text but still flexible enough to support a caption with its own HTML.
↩︎The nice thing about this approach is that it doesn’t require much additional code to support this flexibility, and the flexibility is entirely optional for anybody using the helper. (Figure 12)
module FigureHelper # ... def figure(src, alt:, caption: nil, &block_caption) caption ||= capture(&block_caption) locals = { src:, alt:, figcaption: caption } render partial: "shared/figure", locals: locals end # ...end By gracefully accepting the caption via keyword parameter or block, we default to keeping the most common case simple while supporting a little extra flexibility with the slightly-more-verbose block syntax.
↩︎We’ve seen how helpers can work work hand-in-hand with partials, but, just like partials can be used without a helper, helpers can create markup without needing a partial.
Skip the Partial Completely
We saw in the all-ERB example that a partial can be written entirely in ERb, and since it’s ERb, we could even put the code entirely within a helper and skip the partial file. (Figure 13)
If we used this code in a helper method instead, it streamlines the code a bit and gives us more fine-grained control without an extra .html.erb file. (Figure 14)
In some ways it’s minor, but there’s definitely some value in not needing the extra file.
module FigureHelper # ... 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 # ...end If we used ERb and Rails tag helpers for 100% of the elements and attributes, we’d likely find it more efficient to define everything within a helper method and skip the partial entirely. This approach, however, still unnecessarily uses Rails tag helpers for the figure and figcaption elements.
As far as Ruby goes, this method is perfectly reasonable. Even considering HTML and ERb, there’s no clear case against this approach. It’s concise. It creates and returns a single figure element. Its weakness stems from leaning too heavily towards the back-end when creating front-end code.
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.
On Finding Balance
Everything depends. Context matters both in the code and on the team creating the code. Whether an application leans toward more front-end flavored or back-end flavored code will depend on the skill sets of the people reading and writing that code.
While I don’t have a concrete set of rules, I’ve adopted some rough guidelines that help clarify what approach would be best in a given context. As with any guideline, each of these has its own exceptions, but they provide just enough of a baseline to require some level of justification to ignore them.
- Conditionals, control structures, and Ruby belong in helpers. Using a single conditional or control structure in a partial is fine. Using two is acceptable. More than that and it’s likely worth pulling some logic into a helper method or using multiple partials.
- Markup (and most tag helpers) belong in partials. While we can absolutely generate markup entirely from within helper methods, I’ve found that it’s best to keep tag helpers and markup within partials so they’re closer to the way they’ll be used in conjunction with markup.
- Added indirection should be justified by other benefits. Don’t use tag helpers when static markup does the job. Be judicious about wrapping partials in their own helper methods when an inline call to
renderworks well enough. Add a helper to streamline reuse or to handle some logic that would otherwise have to be crammed into the partial. - Keep things atomic and composable. Both partials and helpers work best when they do one thing really well. If it’s ever a struggle to name a partial or helper method, it’s likely trying to do too much and can be broken up into more maintainable pieces.
The underlying goal with all of these is more about recognizing that the intersection of front-end and back-end code provides opportunities to create code that’s weighted in one direction or the other. This is the handshake between the two, and these guidelines help make the seams more obvious.
There’s enough subjectivity with any of these approaches that there’s no perfect right or wrong approach. Regardless, there’s a world of difference between a haphazard and deliberate approach with each partial or helper we create.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.