Ruby

A Guide to Rails View Helpers

A Guide to Rails View Helpers

Rails view helpers are Ruby methods that keep logic out of your templates. Rails ships with built-ins like link_to, image_tag, number_to_currency, and tag; your own live in modules under app/helpers. Every helper module is available to every view, so name methods carefully. On Rails 8.1, use helpers to format values and build HTML — never to query the database.

In this guide, we’ll dig into the problem helpers solve, how to organize your own, some hard-earned do’s and don’ts, and the HTML-building helpers Rails gives you. Every example is verified on Rails 8.1.

The Problem with Logic-heavy Rails Views

Logic-heavy views are bad for a lot of reasons. We might end up with logic-heavy views because of the pressure of a crushing deadline, or they might result from inevitable legacy code buildup. New features get introduced along with new edge cases, and it seems harmless to update <%= @post.name.titlecase %> to <%= @post.name.titlecase.gsub("#", "") %>.

But before you know it, this can snowball, even if you don’t keep tacking on little additions to this particular line of code. If you keep doing things like this elsewhere in the view, you end up with an ugly, difficult-to-read mess.

So what do we do? Should we litter our controllers with dozens of instance variables like @normalized_titlecased_post_name = @post.name.titlecase.gsub("#", "")? Bloat our models with tons of methods intended only for use in our views? Probably not.

Enter Rails helpers.

What Are Helpers in Rails?

A helper is a method intended to abstract logic out of our views, leaving them more readable, less cluttered, and not directly responsible for processing what they’ve been passed by our controllers.

Rails already ships with a lot of useful helpers to begin with. Some you’re likely already familiar with, like form_with, link_to, and image_tag, to name a few. (Form helpers are a topic of their own — our guide to Rails forms covers form_with in depth.)

Most helpers you write will be specific to your app, but many of Rails’ built-ins will still earn a place.

If you haven’t already, familiarize yourself with the helpers Rails provides — otherwise, there’s a decent chance you’ll end up trying to solve a problem that’s already been solved.

I once wrote a method early in my Rails career to convert a number to a dollar amount, only to discover later that Rails already had a solution — number_to_currency!

Organizing Your Helpers in Rails

By default, a new Rails application has one helper module — ApplicationHelper. You’ve likely noticed that any time you create a controller via rails g controller, Rails automatically creates a new (pluralized) helper module for you as well.

While this form of organization (having a helper module for each controller) is both useful and important, it’s also a bit deceptive. The methods defined in any of these helpers are available in all your views. So if you define my_generic_method() in, say, UsersHelper, and an identically-named my_generic_method() in TransactionsHelper, they won’t only be available in their respective views — one will silently overwrite the other and almost certainly break something.

So you should be fairly thoughtful when naming your helper methods. Don’t assume there’s an invisible User:: namespace or user_ prefix in front of the methods in your UsersHelper, because there isn’t, real or implied.

Any helper methods generic enough to be used all over (most of these probably won’t be model-specific) can go in your ApplicationHelper. Methods specific to view folders (e.g., /views/users) belong to helpers of the same name.

This grouping is purely organizational — but User-specific methods in your ZooAnimalsHelper will confuse other developers (and your future self).

Note: You can turn this behavior off and scope each helper to its own controller’s views by setting config.action_controller.include_all_helpers = false — the behavior Rails had before Rails 4.

Do's and Don'ts for Helpers

Helpers in Controllers

You can use helpers in controllers too — reach them via the helpers proxy (e.g. helpers.number_to_currency).

It’s tempting to abuse this — don’t. If you must, use the helpers proxy rather than include on the entire helper module: include dumps every method defined in that helper into the controller, potentially conflicting with your controller methods.

If you keep finding you want to share a helper across different layers of your app, consider whether it might be better off elsewhere, like in a model.

Helpers and Instance Variables

Helpers can access any instance variables available for the view they’re called in. You can reference those instance variables in a helper without passing them as arguments — but don’t.

Instead, pass your instance variables to your helpers as regular arguments. Doing otherwise tightly couples your controllers and views (and helpers), making reuse, refactoring, testing, and debugging difficult.

On rare occasions, you might want to reference instance variables that aren’t passed as method arguments inside your helpers. You could have a helper method that’s so specific, it only belongs in one spot, or the opposite — it only relies on an instance variable you have everywhere.

This is still somewhat iffy, and ultimately it’s up to you — but if you’re not certain, err on the side of caution.

Never Modify Objects with Helpers

A helper should never modify the state of a passed object or (even worse) modify that object in the database. Remember, they’re responsible for helping the View display things cleanly, not changing anything.

Never Make Database Calls with Helpers

Helpers should also never be responsible for making calls to the database — no part of the view should. Doing so violates the separation of concerns. It also confuses other developers (where is this query coming from?) and makes problems hard to track down. I once spent hours completely baffled by where an N+1 was being introduced, only to discover some queries inside a loop embedded in the view.

A query buried in a helper is exactly the kind of N+1 you only meet in production. AppSignal’s Rails performance monitoring flags N+1 queries per request and points at the line that ran them, so a helper that quietly hits the database shows up before your users feel it.

Don't Let Helpers Become a Dumping Ground

One final word of caution: don’t let your helpers become a dumping ground for one-off methods and snippets that either serve little purpose, or might be better off as another kind of method. They should be relatively simple methods, and only concerned with the view; business logic belongs in our models.

Generating HTML with Rails Helpers for Views: An Example

Rails helpers can work with and generate HTML for our views. The modern entry point is the tag helpertag.div, tag.span, and so on — with content_tag as its longer-standing equivalent. Both build HTML safely from Ruby.

Say we have a zoo and want to display a list of exhibit times for a given group of animals a customer selects. We might have something like this in our view:

erb
<div>
  <% @animals.map do |animal| %>
    <div class="time-heading">
      <%= animal.name %> (<i><%= animal.species.name %></i>): <%= animal.exhibit_time.in_time_zone(animal.exhibit_time.time_zone).strftime("%m/%d/%Y %Z") %>
    </div>
  <% end %>
</div>

There are a few issues with this. For one, it’s hard to read. We’re also calling things like in_time_zone and strftime to format and display the exhibit time, along with a lot of method chaining.

It’s also in no way reusable. Say we need to display this (or something similar) in different places on our site. If we then later decide it needs a style or content update, we have to change it everywhere. This is annoying, and it can be tricky to find every instance when they’re all slightly different.

Here’s the same markup built in a helper:

Ruby
# app/helpers/animals_helper.rb
module AnimalsHelper
  def display_exhibit_times(animals, dom_class: nil, style: nil)
    exhibit_tags = animals.map { |animal|
      exhibit_string = "#{animal.name} (<i>#{animal.species.name}</i>): #{animal.exhibit_time.formatted_start_time}".html_safe
      content_tag(:div, exhibit_string, class: dom_class, style: style)
    }
 
    safe_join(exhibit_tags)
  end
end

Admittedly, this may not be the prettiest example, but it’s considerably easier to read and, more importantly, removes the logic from our view entirely. It’s also reusable and flexible; notice the two named arguments we’ve passed in — dom_class and style. Because content_tag allows us to pass in HTML class and style information, we can pass this in any time we call this method to customize it in different places as needed.

The time formatting also moved into a call on exhibit_time — not every method used in a view has to be a helper. This might be a model method, or a mixin shared across models with times and dates. (That isn’t to say it might not also make sense to use a helper here instead; it might.)

Note: .html_safe marks the string containing the <i> tags as trusted HTML, and safe_join combines the tags into a single HTML-safe string. Joining them with plain .join returns an ordinary String, which <%= %> escapes — the page would show the markup itself instead of your divs.

Our view now becomes:

erb
<%= display_exhibit_times(@animals, dom_class: "time-heading") %>

Isn’t that better?

A Second Example

We need to email our customers their tickets, and we’d like to provide them with an online menu for a dining area at our zoo.

In both cases, we’ll probably want to render QR codes. RQRCode is a gem that can generate them for us:

erb
<div>
  <%= RQRCode::QRCode.new(@ticket.qr_code).as_svg(color: "black", shape_rendering: "crispEdges", module_size: 5, standalone: true, use_path: true) %>
</div>

This isn’t ideal for two reasons. First, we’re instantiating objects in our view. For another, we’re passing every single required option, when most of them will probably be the same throughout our app.

Moving this into a helper cleans it up:

Ruby
# app/helpers/application_helper.rb
def render_qr_code(code, options = {})
  qr_code = RQRCode::QRCode.new(code)
 
  defaults = {
    color: "black",
    shape_rendering: "crispEdges",
    module_size: 3,
    standalone: true,
    use_path: true
  }
 
  qr_code.as_svg(defaults.merge(options)).html_safe
end

Our view becomes:

erb
<div>
  <%= render_qr_code(@ticket.qr_code, module_size: 5) %>
</div>

Much cleaner, and now we can reuse it for our restaurant!

One More Trick: The tag Helper

Do you ever find yourself interpolating conditional class names in your elements?

Say we’re listing our animal exhibits on our main page, and want to highlight certain exhibits if they’re currently featured:

erb
<div class="border-black margin-standard <%= 'bg-featured text-bold' if @exhibit.featured? %>">
  <%= @exhibit.name %>
</div>

You can set conditional classes using the tag helper instead:

erb
<%= tag.div class: ["border-black margin-standard", "bg-featured text-bold": @exhibit.featured?] do %>
  <%= @exhibit.name %>
<% end %>

The classes bg-featured and text-bold will only be included if it’s a featured exhibit. This can make things easier to read, especially if you’ve got multiple classes with multiple conditions!

When you want the class string without the surrounding tag — to pass to a partial, a component, or a plain HTML attribute — class_names builds the same conditional list on its own:

erb
<%= class_names("border-black margin-standard", "bg-featured text-bold": @exhibit.featured?) %>
<%# => "border-black margin-standard bg-featured text-bold" when featured %>
<%# => "border-black margin-standard" otherwise %>

class_names is an alias of token_list, which builds the same kind of space-separated list for any tokens, not only CSS classes — the value of an aria-describedby attribute, for example.

There Isn't Always a 'Perfect' Solution

So you’ve been using helpers for a while now and are comfortable with them. You’ve refactored your views, pulled methods out of your models and your controllers, and everything’s cleaner, easier to read and test. You’ve noticed you’re introducing fewer bugs into your application.

And yet — you still have a bit of logic in some of your views, a few calls to helpers in your controllers, and a few methods that don’t belong in the right place.

That’s completely fine. This is normal and, at least in my opinion, inevitable. Some devs claim there’s always a solution — be it a helper or model method, presenter/decorator class, etc., but personally, I’m not convinced.

Suppose you continually find you want to use a particular method across multiple layers of your app. In that case, it might be a good candidate for a concern, model method, or part of a separate module. And when what you’re extracting is markup rather than logic, a partial is often the better home — especially now that strict locals give partials a declared signature. But sometimes, there isn’t an absolutely perfect solution for every use case or a perfect place to put code. There’s certainly no such thing as a ‘perfect’ app. And that’s okay.

Wrapping Up

Helpers keep logic out of your views: organize them thoughtfully, keep them away from your database, and let content_tag, tag, class_names, and safe_join build your HTML.

Happy coding!

P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

Frequently asked questions

What are helpers in Ruby on Rails?
Helpers are Ruby methods that keep logic out of your view templates. Rails ships with built-ins like link_to, image_tag, and number_to_currency, and your own live in modules under app/helpers. Use them to format values and build HTML so templates stay readable.
How do I add conditional CSS classes with the Rails tag helper?
Pass an array as the class option: static class strings first, then a hash whose keys are class names and whose values are conditions. Rails renders only the classes whose conditions are true. The class_names helper returns the same conditional string for use anywhere.
Should Rails helpers make database calls?
No. Helpers should never query the database — no part of the view layer should. A query hidden in a helper blurs the separation of concerns and is a classic source of N+1 queries. Load data in the controller and pass it to the helper as an argument.
Where are Rails helper methods available?
Every helper module in app/helpers is mixed into every view, so a method defined in UsersHelper works in all templates, not only user views. The per-controller modules are an organizational convention. In controllers, reach view helpers through the helpers proxy.

Published , Updated

Wondering what you can do next?

  • Share this article on social media
Daniel Lempesis

Daniel Lempesis

Our guest author Daniel is a software engineer passionate about Ruby, Rails and software development in general. Most days he can be found squashing bugs or working on building out a new feature.

All articles by Daniel Lempesis

Become our next author!

Find out more
$appsignal install

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal