In Rails, helpers are global view methods that are available to views and are meant to abstract logic out of them, making them more readable and maintainable.
They are usually in charge of formatting data, generating HTML that depends on state or is a result of some logic and other operations that are related to the view like CSS or even JavaScript generation.
Depending on the way we approach them, view helpers
Let's start by understanding the problem they solve:
Logic in views
You might have heard that having logic in views is awful, but rendering them without using any logic is not really doable.
The problem is that logic in views usually:
- Makes them less readable.
- Increases the cognitive load required to understand what's actually happening.
- Difficult maintaining changes by having code subject to change all around the codebase. This is especially important because a single model or resource might be used in multiple views, so a small change can become a hassle to maintain.
- Can deteriorate performance if we're not observant.
You might be wondering, what does logic in views look like?
It's usually something like this:
<%# app/views/books/show.html.erb %>
<div class="<%= @book.free_access? ? 'bg-orange-200' : 'bg-white' %>">
<% if @book.cover.present? %>
<div class="w-48 h-auto overflow-hidden rounded-md">
<%= image_tag url_for(@book.cover), class: "w-full h-full object-cover" %>
</div>
<% else %>
<div class="w-48 h-auto flex items-center justify-center bg-neutral-200 rounded-md">
<p>No cover available</p>
</div>
<% end %>
<% if current_user.subscribed? || @book.free_access? %>
<%# Show the actual book content %>
<% elsif current_user.trialing? && @book.sampleable? %>
<%# Show a book sample %>
<% else %>
<p>Sorry, you don't have access to this book</p>
<% end %>
</div>
Of course, this is a complete fantasy scenario and the logic might look even scarier in reality, but you can see that things can easily get out of hands quickly.
Luckily, there are many techniques we can use to minimize the amount of logic we put into the views, Rails view helpers being one of them.
What are view helpers
In Rails, view helpers are view-accessible global methods that are intended to help us remove logic from views.
Even if any global method that is used in views can fit the definition, when referring to view helpers, we mean those that are defined in modules within the app/helpers
directory.
They can produce strings, simple elements or even fully-fledged components using other Rails provided helpers like content_tag
or button_tag
which can be used to generate HTML using Ruby.
If you've used Rails, you've probably used view helpers, even if you're not familiar with them: the <%= link_to %>
is actually a view helper provided by Rails.
Let's see an example of a simple helper that produces a string:
# app/helpers/users_helper.rb
module UsersHelper
def dashboard_greeting(user)
if user.confirmed?
"Welcome back #{user.username}, nice to see you around!"
else
"Please confirm your account before going further."
end
end
end
Used in the view:
<%# app/views/admin/dashboard.html.erb %>
<div>
<%= dashboard_greeting(current_user) %>
</div
We can also use helpers to generate HTML:
module UsersHelper
def avatar_for(user)
content_tag :div, class: "w-12 h-12 rounded-full overflow-hidden" do
image_tag url_for(user.avatar), class: "w-full h-full object-cover"
end
end
end
When used in the view:
<div class="flex items-center space-x-6 mt-4">
<%= avatar_for(current_user) %>
<div class="flex flex-col">
<h2 class="text-2xl font-bold">Dashboard</h2>
<p class="text-gray-600 text-lg"><%= dashboard_greeting(current_user) %></p>
</div>
</div>
It produces the HTML to display the user's avatar with the greeting:
Note that helpers like dashboard_greeting
allows us to avoid having presentation logic in our models, which are definitely not a good place for it.
Built-in Rails helpers
Rails provides many view helpers out of the box to reduce the amount of code we need to write.
Whenever we're considering writing a helper, it's a good idea to research if their Rails doesn't have that functionality built-in already.
There are too many to define them here, but some helpers that are worth mentioning are:
-
Form helpers: they allow us to quickly build forms and avoid writing boilerplate code over and over again. They are especially useful to formatting the form elements
name
attribute so it matches the format that Rails is expecting. -
Navigation helpers: these include helpers like
link_to
,button_to
andurl_for
. They generate elements that allow -
Text and formatting helpers: helpers like
truncate
orpluralize
are used to manipulate text in useful ways. -
Date and time helpers: used to present dates and time in human-readable formats like:
time_ago_in_words
ordistance_of_time_in_words
. -
Translation: helpers like
t
ortranslate
andl
orlocalize
are used to internationalize Rails applications.
There are many other helper types, and Rails adds them constantly whenever they are useful for new features or the improvement of existing ones.
View helpers best practices
Considering that helpers are global and can allow us to do almost anything we want, common sense is probably the best approach when using them.
However, the following are sound practices when using helpers:
Namespace helpers to avoid collisions
The first thing to consider with view helpers is that their global nature makes them somewhat fragile in large codebases.
Yes, they're defined within a module, but they remain global and having two helpers with the same name, even if located in different modules, will have one override the other, producing potentially unwanted results.
A solution for this is to manually namespace them using the resource's name as a prefix: status_tag(order)
becomes order_status_tag(order)
.
At risk contradicting the advice: if we find ourselves worrying too much about name spacing, you probably have too many helpers in your application, and it might be time to consider other alternatives.
No side effects in helpers
Access to objects within helpers doesn't mean we should ever modify them or produce side effects.
If it receives an object as an argument, we should never modify it's state, persist anything to the database or produce side effects.
Otherwise, we will have very hard to debug behavior in our application, and we will waste time trying to fix it.
Keep them simple and testable
Testing helpers shouldn't be a hassle.
If we keep them simple and give them a single responsibility, writing tests become a simple exercise of generating scenarios and expected results.
They should be small and focused and rarely exceed 8 or 10 lines of code. Naturally, this is not a hard limit, yet it can be
Performance considerations
Performance issues that generate in helpers are usually difficult to debug.
That's why we should avoid querying the database within our helpers at all costs, and we should also take other performance advice into consideration.
Avoiding nested loops, memoizing complex or expensive calculations, and using caching whenever possible and convenient.
Alternatives to helpers in Rails
Even though helpers are pretty useful, they should typically be considered a starting point.
There's nothing wrong with them, but they can become a bit of a hassle to maintain when our application grows, especially if we use them to generate HTML components that might get very logic-heavy.
A practical alternative to helpers is to start moving logic to presenters or decorators, which offer a nice object-oriented way to abstract view logic away from views and even from view helpers.
Besides them, the next step is using something like ViewComponent or Phlex which offer a practical way to build component-based UIs which are easier to maintain, test and modularize.
We can even use libraries like Class Variants to produce clean component variants in Rails to further clean up our views.
Summary
In Rails, view helpers are global methods made to abstract logic from views to keep them more maintainable over time.
They are defined in modules within the app/helpers
directory, they are global and can produce anything from simple strings to fully formed HTML components.
Best practices include name spacing helpers to prevent collisions, avoiding side effects, keeping them simple and testable, and considering performance implications by avoiding database queries and optimizing calculations.
As applications grow, helpers might become too limiting or the trade off we make to use them not worth it.
That's where solutions like decorators, presenters, view objects or view components become attractive.
Like many other things with Rails, helpers are a very nice feature that might become an issue if we're not careful about how we use them.