Rails ActiveRecord Concerns

rails, activerecord,

Swapnil Gourshete Swapnil Gourshete Follow Jul 21, 2024 · 3 mins read
Rails ActiveRecord Concerns


ActiveRecord Concerns are a powerful feature in Ruby on Rails that allow you to extract common code from your models and share it across multiple classes. They provide a clean and modular way to organize your code, promoting the DRY (Don’t Repeat Yourself) principle. In this post, we’ll dive into what Concerns are, how to use them effectively, and some best practices.


What are ActiveRecord Concerns?

Concerns in Rails are modules that extend ActiveSupport::Concern. They allow you to encapsulate related methods, callbacks, and validations into reusable units that can be mixed into multiple models. This is particularly useful when you have functionality that spans across different models but doesn’t warrant a full-blown class inheritance.


Creating a Concern

Let’s create a simple Concern to demonstrate. Imagine we have multiple models that need timestamping functionality:

# app/models/concerns/timestampable.rb
module Timestampable
  extend ActiveSupport::Concern

  included do
    before_save :set_timestamp
  end

  def set_timestamp
    self.timestamp = Time.current
  end
end


Using a Concern in Models

To use this Concern in a model, you simply include it:

class Article < ApplicationRecord
  include Timestampable
end

class Comment < ApplicationRecord
  include Timestampable
end

Now both Article and Comment will have the set_timestamp method and the before_save callback.


Advanced Usage: Class Methods and Instance Methods

Concerns can include both instance and class methods:

module Searchable
  extend ActiveSupport::Concern

  included do
    scope :search, ->(query) { where("title LIKE ?", "%#{query}%") }
  end

  class_methods do
    def top_results(limit = 5)
      order(views: :desc).limit(limit)
    end
  end

  def update_search_index
    # Logic to update search index
  end
end

When included in a model, this Concern adds a search scope, a class method top_results, and an instance method update_search_index.


Best Practices for Using Concerns

  1. Keep Concerns Focused: Each Concern should have a single, well-defined responsibility.
  2. Name Concerns Descriptively: Use names that clearly indicate the functionality, like Searchable, Archivable, or Taggable.
  3. Use included Block Wisely: The included block is executed in the context of the including class, making it perfect for defining callbacks, scopes, and other class-level configurations.
  4. Avoid Deep Nesting: While Concerns can include other Concerns, be cautious about creating deep hierarchies that can become hard to understand.
  5. Document Your Concerns: Especially if they’re used across multiple models, good documentation can save time and prevent confusion.


When to Use Concerns vs. Inheritance

  • Use Concerns when you have behavior that’s shared across unrelated models.
  • Use inheritance when you have a clear “is-a” relationship between models.


Testing Concerns

Don’t forget to test your Concerns! You can test them in isolation:

RSpec.describe Timestampable do
  let(:dummy_class) { Class.new { include Timestampable } }
  let(:dummy_instance) { dummy_class.new }

  it "sets timestamp before save" do
    expect(dummy_instance).to receive(:set_timestamp)
    dummy_instance.run_callbacks(:save)
  end
end


Conclusion

ActiveRecord Concerns are a powerful tool in the Rails developer’s toolkit. They allow for clean, modular, and reusable code across your models. By extracting common functionality into Concerns, you can keep your models lean and focused, while still benefiting from shared behavior where needed.

Concerns should be used judiciously. They’re excellent for sharing behavior across unrelated models, but shouldn’t be used as a catch-all for any shared code. When used correctly, they can significantly improve the organization and maintainability of your Rails application.


Swapnil Gourshete
Written by Swapnil Gourshete Follow
Hi I am Swapnil, a Software Engineer and computer science enthusiastic