Rails Model and Controller Concerns with Example Code

Rails Model and Controller Concerns with Example

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.

Scroll to Top