Helpers vs. Concerns in Rails
What is a helper?
In Rails, a helper is a method that is used in our Rails views to share reused code across them.
When should I create a helper method?
When we have logic that produces bits of html, such as formatting a string or conditionally rendering page elements, we can consider using a helper method to clean up we code.
How do I write a helper method?
Helpers are modules that fall under app/helpers
in our Rails project. Within the module, we can define methods that will be available in all our views.
For example, say we have a User
model with attributes first_name
and last_name
and we'd like to display a user
's full name in a view. We could write a helper to re-use:
# app/helpers/user_helper.rb
module UserHelper
def name_display(user)
"#{user.first_name user.last_name}"
end
end
In our view:
# app/views/users/index.haml
%h1 Users Index
%ul
- User.all.each do |user|
%li= name_display(user)
Can we use them in controllers?
If we're using Rails 5+, we can use helpers in our controller with the helpers
object. Note that it is possible, but it's not super common.
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
@user_name = helpers.name_display(@user)
end
end
What is a concern?
Concerns are modules that inherit from ActiveSupport::Concern
and can be included in controllers or models. The main difference between concerns and regular modules are convenient features such as #included
and class_methods
. The included
block is called whenever a module is “included” into another class or module. It usually includes important class methods / callbacks that we want shared across the models using the concern.
When should I create a concern?
- When we have some sort of functionality that is shared across different models. For example, say we have an
#archive
function which setsarchived_at
forUser
andPost
. Instead of writing that method in both models, we can write a concern andinclude Archivable
instead. - When we have some sort of functionality that is shared across different controllers. For example, say we are using the pundit gem to handle authorization in an application that enables users to make posts, comment on posts, and like posts. Say we want to skip authorization in actions related to
Posts
andComments
. We could write a concern that gets included in both controllers.
How do I write a concern?
Concerns live in either app/controllers/concerns
or app/models/concerns
.
Below, I will demonstrate how to write the concerns for the scenarios discussed above.
Model Concerns
Concern: multiple models can be archived by setting the archived_at
attribute on the model. We can write an Archivable
concern and include it in the necessary models.
The concern:
# app/models/archivable.rb
module Archivable
extend ActiveSupport::Concern
included do
scope :unarchived, -> { where(archived_at: nil) }
scope :archived, -> { where.not(archived_at: nil) }
end
def archive
self.archived_at = Time.now
save validate: false
end
def archived?
!archived_at.nil?
end
end
Including it in User
:
# app/models.user.rb
class User < ApplicationRecord
include Archivable
...
end
And again in Post
:
# app/models.post.rb
class Post < ApplicationRecord
include Archivable
...
end
Now, we can call the methods defined in the module for instances of User
and Post
, such as User.last.archive
.
Controller Concerns
Concern: multiple controllers should skip authorization (via pundit). Instead of calling the same callbacks twice, we define a concern and include it in the controller.
The concern:
# app/controllers/concerns/skip_authorization.rb
module SkipAuthorization
extend ActiveSupport::Concern
included do
skip_after_action :verify_authorized
skip_after_action :verify_policy_scoped
end
end
Including it in our posts_controller
:
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
include SkipAuthorization
...
end
And again in comments_controller
:
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
include SkipAuthorization
...
end
What's the difference between a helper and a concern?
To state it simply, helpers contain methods to be used in views. Concerns contain methods to be included in controllers/models.
Additional Resources
Previous post: Decorator Pattern in Ruby on Rails