In Ruby on Rails, building clean, modular code for large applications requires dividing complex logic. Instead of overloading models and controllers with too many methods, concerns provide a great solution for organizing and reusing code. This guide explores the benefits of using concerns in Rails, with practical examples of implementation.
What Are Rails Concerns?
Concerns in Rails are modules that encapsulate logic you can share across models or controllers. When logic fits into multiple classes, concerns prevent code duplication, making it easier to maintain. Rails provides concerns for both models and controllers, which live in the app/models/concerns
and app/controllers/concerns
directories, respectively.
Why Use Concerns?
Without concerns, you might end up with “fat models” or “fat controllers” stuffed with functionality, making them difficult to read and prone to bugs. By using concerns, you can:
- Keep code organized and focused.
- Make code reusable across different models or controllers.
- Improve testability by isolating logic into modules.
Setting Up Rails Model Concerns
Imagine you’re building a Rails application for an online store. You might have models like Product
and Category
, each of which needs logic to handle popularity (based on sales). Let’s encapsulate this logic in a concern.
Example: Creating a Rails Model Concern
Define a Concern in app/models/concerns/popular.rb
:
# app/models/concerns/popular.rb
module Popular
extend ActiveSupport::Concern
included do
scope :popular, -> { where('sales_count > ?', 50) }
end
def popular?
sales_count > 50
end
end
Here, we define a Popular
concern with a popular
scope and a popular?
instance method.
The scope :popular
filters for records with sales counts over 50, while popular?
returns true
or false
based on the sales count.
Include the Concern in Models:
# app/models/product.rb
class Product < ApplicationRecord
include Popular
end
# app/models/category.rb
class Category < ApplicationRecord
include Popular
end
Using the Concern in Code:
You can now call Product.popular
or Category.popular
to fetch popular products or categories.
Use @product.popular?
to check if a single product is popular.
Why This Is Helpful
If you didn’t use a concern, you’d have to copy-paste the popular
scope and method into each model, resulting in repetitive code. By using the Popular
concern, you keep the logic in one place, making it easier to maintain.
Setting Up Rails Controller Concerns
Let’s say you have an admin area in your application. Different controllers within the admin namespace might require authorization logic. Instead of duplicating this logic across all admin controllers, use a controller concern.
Example: Creating a Rails Controller Concern
Define a Concern in app/controllers/concerns/admin_authorization.rb
:
# app/controllers/concerns/admin_authorization.rb
module AdminAuthorization
extend ActiveSupport::Concern
included do
before_action :authorize_admin
end
private
def authorize_admin
redirect_to root_path unless current_user&.admin?
end
end
The AdminAuthorization
concern includes a before_action
to call authorize_admin
.
The authorize_admin
method checks if the user is an admin and redirects non-admins to the homepage.
Include the Concern in Admin Controllers:
# app/controllers/admin/products_controller.rb
class Admin::ProductsController < ApplicationController
include AdminAuthorization
def index
# Only accessible by admins
end
end
# app/controllers/admin/categories_controller.rb
class Admin::CategoriesController < ApplicationController
include AdminAuthorization
def index
# Only accessible by admins
end
end
How This Simplifies Your Code Without the concern, you’d need to write before_action :authorize_admin
in every admin controller, increasing the likelihood of errors. Concerns make it easy to implement logic consistently across multiple controllers.
Tips for Using Rails Concerns Effectively
1. Keep Concerns Focused
- Concerns should group related logic. Avoid adding unrelated methods to a single concern.
- For example, separate a
Popular
concern from other methods that might relate to pricing or stock, keeping each responsibility isolated.
2. Name Concerns Clearly
- Names should reflect the functionality they provide. For instance,
AdminAuthorization
clearly suggests that the concern handles authorization for admins.
3. Test Concerns Thoroughly
- Since concerns are shared across multiple models or controllers, it’s crucial to test them in isolation to ensure they work as expected.
4. Avoid Overusing Concerns
- Overuse of concerns can lead to scattered logic, making it difficult to track functionality. If a concern becomes too large, consider splitting it or moving logic to a service object.
Advanced Example of Combining Multiple Concerns
Let’s add another layer. Suppose each model in your online store also needs tagging functionality. You can combine multiple concerns in a single model.
Define a Taggable Concern in app/models/concerns/taggable.rb
:
# app/models/concerns/taggable.rb
module Taggable
extend ActiveSupport::Concern
included do
has_many :tags, as: :taggable
end
def add_tag(name)
tags.create(name: name)
end
end
The Taggable
concern allows models to have many tags and adds an add_tag
method for creating new tags.
Include Multiple Concerns in Models:
# app/models/product.rb
class Product < ApplicationRecord
include Popular
include Taggable
end
Now, Product
has both popular
functionality from Popular
and tagging
functionality from Taggable
.
This allows you to chain methods like Product.popular
and also call @product.add_tag("new")
.
Common Pitfalls to Avoid with Rails Concerns
- Avoid “God” Concerns: Don’t let concerns become “catch-all” modules with unrelated methods.
- Test Dependencies Carefully: If one concern relies on another, test them together to avoid unexpected errors.
- Prioritize Readability: Don’t split code across concerns to the point where the functionality becomes difficult to understand.
Conclusion
Rails concerns simplifying code organization, and making applications more maintainable and modular. Whether used in models or controllers, concerns help developers keep their code DRY (Don’t Repeat Yourself). By using concerns correctly, you’ll create Rails applications that are not only efficient but also easier to test and extend.
Concerns are a powerful tool. Experiment with them to see how they can improve your code structure, save time, and make your Rails applications cleaner and more scalable.