Daniel
Posted on February 8, 2023
Views in Rails don't do much besides showcasing what we want. Sure, they might render slightly different results depending on who's watching (an admin or a logged-in user has a different experience than a guest user, for example), but they don't do much processing on what they're given.
Or at least they shouldn't. Often though, Rails projects wind up with a lot of logic in their views.
In this post, we'll explore how to use Rails helpers to keep our views clean and readable.
But first, let's see why too much logic in our views can become problematic.
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.
Most of the helpers you write yourself will naturally be much more specific to your app than what Rails includes, but you will likely still find many Rails helpers very useful.
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 — ApplicationHelper
— which all other helpers should inherit from. 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 simply 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.
Again, this is strictly for organizational purposes, given all helper methods are available to all views. But, as you might imagine, it will definitely confuse other developers (and likely your future self) if you end up finding User
-specific methods in your ZooAnimalsHelper
.
Note: Before Rails 4, this was not the case. You can turn this behavior off by setting config.action_controller.include_all_helpers = false
in your application.rb
.
Do's and Don'ts for Helpers
Helpers in Controllers
You can include helpers in controllers. In modern Rails versions (5+), you can simply access them in your controllers via helpers
(e.g. helpers.number_to_currency
).
It's often tempting, and you can absolutely abuse this — I won't pretend I never have. But generally, you shouldn’t. It's okay to do this occasionally, and if you must, do use the helpers
method rather than include
on the entire helper module in your controller. 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. The instance variables can be referenced inside your helpers without being passed explicitly as method arguments, but this is not advised.
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 leverages an instance variable you have everywhere.
This is still somewhat iffy, but ultimately it's up to you. I will say though that if you're not very certain, err on the side of caution. As with anything else, you'll get a feel for things as you see what does and doesn't work over time.
Never Modify Objects with Helpers
This is probably obvious, but 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. Not only does doing so badly violate the separation of concerns, it's also incredibly confusing to other developers (where is this query coming from?), and can make tracking down problems difficult. 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.
Don't Let Helpers Become a Dumping Ground
One final word of caution: don't, as is so often the case, let your helpers become a dumping ground for one-off methods and little snippets of code 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 really belongs in our models.
Generating HTML with Rails Helpers for Views: An Example
Rails helpers can work with and generate HTML for our views. Remember Rails' built-in helpers? A content_tag will let us generate our own HTML.
Let's 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:
<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 a bit 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. Let's 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 for obvious reasons (in addition to the fact that it can be tricky to find every example if they're all slightly different).
Let's see what handling this in a helper might look like:
# helpers/animals_helper.rb
module AnimalsHelper
def display_exhibit_times(animals, dom_class: nil, style: nil)
exhibit_strings = animals.map { |animal|
"#{animal.name} (<i>#{animal.species.name}</i>): #{animal.exhibit_time.formatted_start_time}".html_safe
}
exhibit_strings.map { |exhibit_string|
content_tag(:div, "#{exhibit_string}", class: dom_class, style: style)
}.join
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.
Another thing you may have noticed is the transformation of the time formatting into a call from exhibit_time
. This is just to point out that not all methods used in the view have to be helpers; this might be a model method, or a mixin shared across models with times and dates for easier formatting. (That isn't to say it might not also make sense to use a helper here instead; it might.)
Note: .html_safe
is needed to translate the </i>
tag to HTML. .join
is required to return multiple content tags for use in the DOM.
Our view now becomes:
<%= display_exhibit_times(@animals, dom_class: "time-heading") %>
Isn't that better?
A Second Example
Let's consider another 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. Let's use RQRCode
, a gem that can do just that for us:
<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 a number of reasons. For one thing, 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.
Let's move this to a helper:
# 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:
<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:
<div class="border-black margin-standard <%= 'bg-featured text-bold' if @exhibit.featured? %>">
<%= @exhibit.name %>
</div>
As of Rails 6.1, you can set conditional classes using the tag
helper.
<%= 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!
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. 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
In this article, we explored how to make your Rails views cleaner and more readable using helpers. We looked at how to organize your helpers, some do's and don'ts, and how to generate HTML for your views using helpers.
We finally argued that even if you master helpers, you'll likely still never achieve the 'perfect' Rails app. However, you'll certainly have an app that's easier to maintain.
I hope you've found this post useful.
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!
Posted on February 8, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.