Skip to main content

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 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.

Comments

  1. awesome post presented by you..your writing style is fabulous and keep update with your blogs Ruby on Rails Online Course Hyderabad

    ReplyDelete

Post a Comment

Popular posts from this blog

Installing Wowza Streaming Engine on ubuntu

Highcharts with grouped categories

Completely Uninstall and Install Rubymine on ubuntu