Preview UI changes with Ruby on Rails variants

When implementing new design for a live application we usually don’t want our users to see an unfinished implementation, but we might want to give some users the ability to preview it on the live (production or staging) environment, for designers, product owners, our most loyal customers.

Here’s one of the simplest ways to do it without introducing complex feature switching mechanisms.

The Rails way

Fortunately Ruby on Rails got us covered on this one with the help of view variants. In short, it allows us to define several different view templates for the same controller action, and determine which one should be rendered on the controller level.

Let’s say we have a Dashboard#show and want to change the way data is presented. Then we need to create a new view show.html+redesign.erb, so we end up with two views for the same action:

app/views/dashboard/show.html.erb
app/views/dashboard/show.html+redesign.erb

To render dashboard with new design we can explicitly pass the variant name to the action renderer:

def show
  render variants: [:redesign]
end

But since we want it on demand only and don’t want to affect other users, we need a way to switch it on/off. We can do that by requiring users to pass a preview parameter with their request:

def show
  render variants: [:redesign] if params[:preview]
end

Make it reusable

Let’s extract this into a concern so it’ll be easier to reuse for other pages, and also we want to make as little as possible modifications of the exiting code.

Also we can store the preview flag in the session, so that users won’t have to pass the preview param with each request.

module RedesignPreview
  extend ActiveSupport::Concern

  included { before_action :set_redesign_preview }

  class_methods do
    def with_redesign_preview(options = {})
      before_action(options) { set_redesign_variant }
    end
  end

  private

  def set_redesign_variant
    request.variant = :redesign
  end

  def redesign_preview?
    session[:redesign_preview]
  end

  def set_redesign_preview
    session[:redesign_preview] = true if params[:preview].present?
    session.delete(:redesign_preview) if params[:preview_reset].present?
  end
end

And then use it in our DashboardController enabling redesign only for #show action:

class DashboardController < ApplicationController
  include RedesignPreview

  with_redesign_preview only: :show, if: :redesign_preview?
end

Now to preview redesign users can pass the preview parameter and reset back to the usual:

http://example.com/dashboard?preview=1 # to enable preview
http://example.com/dashboard?preview_reset=1 # to disable preview

Customize to your needs

Furthermore, we can extract that and give users an ability to control that from their profile settings for example.

class RedesignPreviewController < ApplicationController
  def create
    session[:redesign_preview] = true
  end

  def destroy
    session.delete(:redesign_preview)
  end
end

Also, we can change our RedesignPreview concern a bit to make it more flexible and support different preview variants:

module RedesignPreview
  extend ActiveSupport::Concern
  
  included { before_action :set_redesign_preview }
  
  class_methods do
    def with_redesign_preview(options = {})
      variant = options.delete(:variant)
      before_action(options) { set_redesign_variant(variant) }
    end
  end
  
  private
  
  def set_redesign_variant(variant)
    request.variant = :redesign
  end
  ...
end

And pass variant as a parameter:

with_redesign_preview only: :show, 
                      if: :redesign_preview?, 
                      variant: :alternate_version

If you need to add a preview for more than one page, you can create variants for layouts and partials as well, and include the concern into your ApplicationController:

class ApplicationController < ActionController::Base
  include RedesignPreview

  with_redesign_preview if: :redesign_preview?
end