ActionPack Variants on Rails

ActionPack Variants on Rails

Rails 4.1 released a little while ago and came out with a fantastic new feature I think a lot of responsive web developers are going to flex. It’s called Variants. ActionPack Variants.

What does it do?
It allows you to create different views for different device formats - such as mobile phones, tablets and desktops.

1
2
3
# /app/views/home/index.html.erb
# /app/views/home/index.html+phone.erb
# /app/views/home/index.html+tablet.erb

Why is this useful?
This is tremendously useful because you can now serve a much slimmer version of your view to mobile devices, instead of just hiding the elements using CSS using media queries.

Hidden elements still load and need to come down ‘the wire’ - using Variants it’s much easier than before to serve up lean and mean views to mobile devices.

Setup
Create a homes controller that will enable you to play around with ActionPack Variants, run the commands below to create the home controller:

1
rails g controller homes show

Then apply a few simple routes to your application to finish setting things up. Open up your routes file (config/routes.rb).

1
2
root to: "homes#show"
resource :home, only: [:show]

Well, Now we are ready to play around with ActionPack Variants. ActionPack Variants works by using a before_action in your application controller. Open up your Application Controller (app/controllers/application_controller.rb) and modify so that it looks sometings like:

application_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 I use it determine whether we are running a mobile, tablet, or desktop device.

Then create a view called show.html+phone.erb (app/views/homes/show.html+phone.erb) for your homes controller and add in the code listed below:

show.html+phone.erb
1
2
3
4
<h1>Phone</h1>
<p>
  You are running on a smart phone.
</p>

Then create a view for our tablet users. Create a new view called show.html+tablet.erb (app/views/homes/show.html+tablet.erb) and add in the code listed below:

show.html+tablet.erb
1
2
3
4
<h1>Tablet</h1>
<p>
  You are running on a tablet.  Whoohoo!
</p>

Then modify your default show.html.erb (app/views/homes/show.html.erb) view. Open it up now and modify it so that it looks something like:

show.html.erb
1
2
3
4
<h1>Desktop</h1>
<p>
  All other users get redirected here.
</p>

There are still a few pieces of the puzzle left. What if you need to detect the device type prior to rendering the view? For example, setting paging size/options for our queries. Fortunately, this is pretty easy. Open up your homes controller (app/views/controllers/homes_controller.rb) and modify it so that it looks like the code listed below:

homes_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
end

There is one more thing to do. What happens if you need to detect the variant in your view? Fortunately this is very easy and be shown,open up your application layout (app/views/layouts/application.html.erb) and modify it so that it looks like the code listed below:

application.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!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?.

So far so good, That’s it! See ya! :)