Skip to content

Instantly share code, notes, and snippets.

@ntamvl
Last active May 12, 2022 14:34
Show Gist options
  • Save ntamvl/aaeb5bc82c6181d951d48241b77cb2f8 to your computer and use it in GitHub Desktop.
Save ntamvl/aaeb5bc82c6181d951d48241b77cb2f8 to your computer and use it in GitHub Desktop.
Controller Concerns in Rails 4

Controller Concerns in Rails 4

If you setup a Rails 4 app, you’ll notice the app/models/concerns and app/controllers/concerns directories. Concerns are modules that can be mixed into your models and controllers to share code between them.

Some developers falsely classify mixins as composition when they are actually a form of inheritance. When you include a module in a class, that module’s methods are added to the inheritance chain just like a parent class’ methods are added to a subclass. So, don’t think you’ve solved the problem of inheritance by simply splitting your inherited code into separate files!

That being said, mixins can be a valuable tool to share code between classes that are otherwise unrelated. Here’s an example of how I chose to use it recently.

I am adding admin reporting features to an app I’m hoping to launch soon. I have an admin controller with a simple before filter to redirect if the current user is not an administrator.

class AdminController < ApplicationController
  before_filter :check_admin_user

  private

  def check_admin_user
    unless current_user.admin
      flash[:alert] = "You can't be here!"
      redirect_to root_path
    end
  end
end

I need two admin reports that allow filtering by month. To stick with my routing scheme, I want a separate controller for each report. Both controllers need to load months from the database, build a list of months, get the selected month from the query string or month list, and find records where a specific date field falls within the given month/year. The only difference is how those records are processed before being used in the views.

These are the only two admin features so far. So, I started by putting the month filtering code in the AdminController so it could easily be shared by both. However, it’s likely that more admin features will be added later that don’t need month filters. More importantly, month filtering isn’t intrinsic to the admin section of the site. The purpose of the AdminController is to prevent non-admins from accessing the actions. That’s it. The month filtering code doesn’t really belong there.

Where should I put it? How about in a concern?

# app/controllers/concerns/month_filtering_for_contests.rb
module MonthFilteringForContests
  extend ActiveSupport::Concern

  included do
    attr_reader :month, :contests
    before_filter :build_month_lists
    before_filter :set_current_month
    before_filter :load_contests_for_current_month
  end

  def build_month_lists
    @months = Contest.months_with_ended_contests

    @month_list = @months.map { |date|
      [date.strftime("%B %Y"), date.strftime("%Y-%m")]
    }
  end

  def set_current_month
    @month =
      if params[:month]
        Date.new(*params[:month].split(/-/).map {|part| part.to_i})
      else
        @months.first
      end
  end

  def load_contests_for_current_month
    @contests = Contest.ended_during(@month)
  end
end

Now, my AdminController can remain clean and the month filtering module can be included in each report controller.

class Admin::SampleReportController < AdminController
  include MonthFilteringForContests

  def show
    @totals = contests.each_with_object({}) { |contest, hash|
      # code to process reporting data here
    }
  end
end

Now that I look at it, one can argue that everything in AdminController can be moved into it’s own concern and the class can be removed completely. However, it’s 3:02am on Christmas Eve. So, that decision can wait.

source: http://elegantbrew.tumblr.com/post/70990048275/controller-concerns-in-rails-4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment