How to add "Preview Mode" style functionality to a Rails application
Tags: rails, beginners
Published: 2018-09-23 11:35:42

---
title: How to add "Preview Mode" style functionality to a Rails application
published: false
description: A quick tutorial on adding a Preview before publishing items.
tags: Rails, Beginner
cover_image: http://res.freestockphotos.biz/pictures/8/8040-illustration-of-a-sneak-preview-sign-pv.png
published: true
---

Implementing a 'Preview Mode' style functionality is deceptivly simple in Rails.  Let's assume we have an application where we introduce items to customers.  We have a normal customer domain, and a back end Admin domain where we perform basic CRUD actions like creating, editing and updating items with information.

Now, our writers want the ability to preview the item they are creating to see how it will look when published.  However, we don't want normal users to be able to access our draft items.  If a user visits an item page that's a draft, they should get a 404 error.  However, writers should be able to visit an item page that is a draft to see how it will look (with a banner or something indicating that the page is in preview mode).  

Let's implement that functionality.  This tutorial will use Cancan for resource authorization, but it can be changed based on whatever library you currently use.

## Add Status column to your Model.

First, we should add a `status` column to our model.  In practice, this column should have either values "draft" or "published".  So, this column should be of type String, and given a default of "draft" so newly created items are not displayed by default.  Also, we should set it so that null is not acceptable.  The following migration will work for our purposes:
```
  # In your Migration file
  
  def change
    add_column :items, :status, :string, default: "draft", null: false
  end
```

## Update the Model

In your `item.rb` model file, we should add some Constants for our statuses, as well as a scope to access them easily in our views.

For our Constants:
```
  # In item.rb
  
  STATUS_PUBLISHED = "published".freeze
  STATUS_DRAFT = "draft".freeze
  enumerize :status, in: [STATUS_PUBLISHED, STATUS_DRAFT]
```
And scopes:
```
  # In item.rb
  
  scope :published, -> { where(status: STATUS_PUBLISHED) }
  scope :draft, -> { where(status: STATUS_DRAFT) }
```

## Change the Items#show lookup method

We need to change how our Items controller looks up items.  You're likely already using a resource authentication library (like CanCan), but we will need to bypass that default functionality.  In the case of Cancan, be sure to exclude the show action from any loading and authorization of resources.

We want to see if there is a 'draft' param being passed to the controller.  If so, we can look for our item in all of the Items.  If the request is not looking for a draft, then we should only look in published Items.
```
  def show
    items = params[:draft].present? ? Item.all : Item.published
    @item = items.find(params[:id])
  end
```
Let's assume we have an item with id: 4, and it's status is `draft`.  If a user visits ~/items/4, they will get a 404 error (because `services.find(params[:id])` will turn up nothing because it's only looking in `Services.published`).  However, if your writers access ~/items/4?draft=true, they will be able to see the item in draft mode.

What about normal users looking up the above url, and they add ?draft=true to it?  Won't they be able to see the item in draft mode?  That's not what we want at all.  Let's make sure only our writers can access that page.

## Update CanCan(or whatever library you use) to remove permission from Normal Users

You need to ensure that normal users are only given read access to items with a status of "published".  Check the documentation for your authorizaion library of choice, but for Cancan you may have something like this:

```
# in models/ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    if user.type == "writer"
      can :manage, Item
    else
      can :read, Item do |item|
        item.status == Item::STATUS_PUBLISHED
      end
    end
  end
end
```
Basically, this ensures that non "writer" users will be able to see Items.  However, the only items they will be able to see are those that are published.  So, if a user attempts to access ~/services/2?draft=true, Cancan will stop them before the ItemsController attempts to find the record.

We now have all the back end code for "Preview Mode" style functionality.  All that remains is updating our Views.

## Add the Ability to Update the Item Status
Wherever you have your form setup to create items, be sure to include a selector to choose if the item is in draft mode, or published. 

Or, you could use buttons when the writer is saving an Item.  The choice is yours, but be sure to give writers the choice when saving Items.

## Update Views for Writers and Normal Users
In the Admin domain, add a way to access the draft view with something like:
```
# in views/admin/items/index.html.haml

# when displaying all the Items
%li= link_to "Preview", service_path(service, draft: true), target: "_blank"
```
Here, we link to the services page in the normal user domain while also including the `draft` param set to `true`.  If you remember, this was used in the ItemsController to decide wether to look up from all Items, or only published Items.

In normal users views, if you're displaying services in places other than Item#show, be sure that you only display services that are published.  Use the previously created `published` scope wherever you display services to users.

For example if you want to list all the items related to a category, you might have had something like this:
```
@category.items.each do |item|
  #display code
end
```
But you need to change it to this:
```
@category.items.published.each do |item|
  #display code
end
```

## Add something to the top of Draft Views
Finally, we want something to signify to writers that the service they are looking at is in Preview mode.  In the normal Service#Show view file, add some HTML at the top:
```
- if params[:draft].present?
  %p.draftMessage You are in Preview Mode
```
Then update the CSS to look however you see fit!

There you have it!  You've now emplemented a basic "Preview Mode" feature into your application.  Good luck and happy coding!