Introduction

Responsive design is often used to provide a nice, clean layout that scales across multiple screen sizes and multiple devices. Unfortunately responsive design also adds a degree of complexity to your website. The amount of CSS, JavaScript, and HTML in your pages increase substantially, which can be a burden for slower devices such as mobile phones, tablets, etc. Fortunately Ruby on Rails 4.1 introduces a new feature to combat this called Action Pack Variants. Action Pack Variants allows you to detect the device type on page load and decide what to send to the browser. This article will show you how to use Action Pack Variants in your Ruby on Rails 4.1+ application.

Application Setup

First, let's create a homes controller that will enable us to play around with Action Pack Variants, run the commands below to create the home controller now.

Terminal Commands:

rails g controller Homes show

Now let's apply a few simple routes to our application to finish setting things up. Open up your routes file and modify it to look like the code listed below.

config/routes.rb:

Rails.application.routes.draw do
  resource :home, only: [:show]
  root to: "homes#show"
end

Great! Now we are ready to play around with Action Pack Variants. Action Pack Variants works by using a before_action in your application controller. Let's create a functional example. Open up your Application Controller and modify so that it looks like the code listed below.

app/controllers/application_controller.rb:

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  before_action :detect_browser

private
  def detect_browser
    case request.user_agent
      when /iPad/i
        request.variant = :tablet
      when /iPhone/i
        request.variant = :phone
      when /Android/i && /mobile/i
        request.variant = :phone
      when /Android/i
        request.variant = :tablet
      when /Windows Phone/i
        request.variant = :phone
      else
        request.variant = :desktop
    end
  end
end

How does it work? Before an action is called, The detect_browser is called and the request.variant is set to whatever you want it to be. In the above example we use it determine whether we are running a mobile, tablet, or desktop device. You can actually use it for anything, like detecting Internet Explorer versions (we finally have an easy way to send compatible versions of our site to IE6/7 users!), Looking for special browsers, etc. P.S. that before_action is the exact same thing as before_filter.

One more thing to note, this has been stated a bazillion times on other sites, but it's worth repeating here. It's not possible to detect every Android tablet out there. Google currently recommends that Android phone manufacturers have the word 'mobile' in their user agent strings, but there are times where this is not the case. You may have to do some digging around to create some manufacturer specific exception code. However, for the majority of use cases, the above code should detect things fairly well. I've also heard that Microsoft doesn't differentiate tablets running it's mobile operating system. At this early stage in the game it's best just to send all users with Windows Phone in their user agent the phone variant of your site.

Great! Now that we know that, where do we go next? Let's start with something simple. Create a view called show.html+phone.erb for your homes controller and add in the code listed below.

app/views/homes/show.html+phone.erb:

<h1>Phone</h1>
<p>
You are running on a smart phone.
</p>

Great! Now let's create a view for our tablet users. Create a new view called show.html+tablet.erb and add in the code listed below.

app/views/homes/show.html+tablet.erb:

<h1>Tablet</h1>
<p>
You are running on a tablet.  Whoohoo!
</p>

Now, let's modify our default show.html.erb view. Open it up now and modify it so that it looks like the following code.

app/views/homes/show.html.erb:

<h1>Desktop</h1>
<p>
All other users get redirected here.
</p>

Great! That's set up now, but how do we test? Thankfully Google Chrome includes a very easy way to test. Depending on what platform you are running, this feature might be hidden. First, start your rails server, navigate to your local Rails App, and open up the Chrome Developer tools. You will see something similar to the screenshot listed below. The bar on the bottom can actually be expanded by clicking /dragging up on the gray border. After doing this, clicking the 'Emulation' tab will allow you to select a device to test with. The testing here is very basic, but should be enough to suite our needs.

Google Chrome Developer Tools Example Screen Shot

Let's test our site now, Select 'Google Nexus 4' (a phone) from the Dropdown, then click emulate. Once we do this, our browser will be detected as a phone and the phone template will display in the middle of our browser. Selecting other devices such as 'Apple iPad 1 / 2 / iPad Mini' will render the tablet view.

That's great, but there are still a few pieces of the puzzle left. What if we need to detect the device type prior to rendering the view? For example, setting paging size/options for our queries. Fortunately, this is relatively easy. Open up your homes controller and modify it so that it looks like the code listed below.

app/views/controllers/homes_controller.rb:

class HomesController < ApplicationController
  def show
    @device = "others"
    respond_to do |format|

        format.html.phone do # phone variant
          # add code here for phones
        end

        format.html.tablet do # tablet variant
          # add code here for tablets
        end

        format.html.desktop do 
          # add code here for desktops and other devices
        end
    end # end respond_to
  end # end show
end # end class

Adding code in the format.html blocks listed above allows you to quickly/easily create special queries that are tailored to your device needs. For example, you can make page sizes smaller for mobile devices or send IE6/7 users to a warning page telling them to upgrade their browsers.

Great! However, there is one more thing to cover. What happens if you need to detect the variant in your view? Fortunately this is very easy and be shown with a simple example. Open up your application layout and modify it so that it looks like the code listed below.

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title>ActionPackVariantsExample</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

<% if request.variant.include? :tablet %>
  <small>Welcome Tablet User</small>
<% elsif request.variant.include? :phone %>
  <small>Welcome Phone User</small>
<% end %>

</body>
</html>

Note the if statement starting with if request.variant.include? :tablet. Why do we use .include? The request.variant is actually an array. Therefore it is necessary to use .include?.

That's it! That's all there is to it! Thanks for reading!