My team is currently working on an app, where different content need to be served depending on what device you use to access the page: desktop, tablet or a phone. Luckily, Rails 4.1 came out with an ActionPack Variants which just do this task. We might want the behaviour application-wide, so we need to share it accross all controllers. The first thought is to put the code in a base controller, that all the other controllers inherit from (eg. ApplicationController), like this:
class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_filter :detect_device private def detect_device case request.user_agent when /iPad/i request.variant = :tablet when /ip(hone|od)/i request.variant = :phone when /Android/i && /mobile/i request.variant = :phone when /Android/i request.variant = :tablet when /Windows Phone/i request.variant = :tablet end end end
Despite the fact it is not particularly DRY (this is a separate feature and it’s probable we’re gona reuse it), the app we’re builing is divided into several zones (client, emploee, admin), where each zone has a separate master controller, that inherits directly from ActionController::Base. The variants behaviour should be present only for client and emploee. How to do it then?
Extract the code to a concern!
When you created a Rails 4 app, you’ve probably noticed new directiories under controllers and models, which is called concerns. The idea of concerns was strongly promoted by DHH and now it is bundled in Rails together with a handy ActiveSupport::Concern wrapper. Let’s do it then, the separate concern will look like:
# coding: utf-8 #app/controllers/concerns/detect_format_variant.rb module DetectFormatVariant extend ActiveSupport::Concern included do before_filter :detect_device end private def detect_device case request.user_agent when /iPad/i request.variant = :tablet when /ip(hone|od)/i request.variant = :phone when /Android/i && /mobile/i request.variant = :phone when /Android/i request.variant = :tablet when /Windows Phone/i request.variant = :tablet end end end
Then in every controller we want this behaviour, we just need to include it:
class ClientController < ActionController::Base protect_from_forgery with: :exception include DetectFormatVariant end
Not only it is DRYer, but it’s also self-descriptive. Concerns help you keep your controller clean and is a perfect tool to share code between controllers.