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.
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?
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.
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.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
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?
Now, my AdminController can remain clean and the month filtering module can be included in each report controller.# 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 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.class Admin::SampleReportController < AdminController include MonthFilteringForContests def show @totals = contests.each_with_object({}) { |contest, hash| # code to process reporting data here } end end
awesome post presented by you..your writing style is fabulous and keep update with your blogs Ruby on Rails Online Course Hyderabad
ReplyDelete