DEV Community

Cover image for How to add "Preview Mode" style functionality to a Rails application
Robert Hustead
Robert Hustead

Posted on

How to add "Preview Mode" style functionality to a Rails application

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

And scopes:

  # In item.rb

  scope :published, -> { where(status: STATUS_PUBLISHED) }
  scope :draft, -> { where(status: STATUS_DRAFT) }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

But you need to change it to this:

@category.items.published.each do |item|
  #display code
end
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen 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!

Top comments (2)

Collapse
 
edisonywh profile image
Edison Yap

I believe it's enough to just do enum status: %i[draft published] and Rails will generate the magic accessor methods for you.

Might be a good idea to make a Constant though so you can reuse it in controller/elsewhere.

api.rubyonrails.org/v5.1/classes/A...

Collapse
 
swiknaba profile image
Lud

With rail's enum you can also use an integer column. Add an index to this database column (with migration it's enough to pass index: true to the add_colmn method), and your scopes will be much faster and lighter on the database :-)