Complementing Shop Content

Scrivito lets you edit content directly on those of your web pages that were created with Scrivito. However, you can also enrich already existing resources with content that can be edited with Scrivito's user-friendly interface.

Think of a RoR-based shop that has already been populated with numerous products. If its interface for maintaining product details is cumbersome, you can have Scrivito provide the editing comfort your users deserve while keeping the content they provide separate from your core data. 

The code samples in this article assume that there is a Product ActiveRecord model, but this approach can easily be adapted to work with other data stores as well. All you need is a unique ID by which your data is identified.

Associating products with CMS objects

After integrating Scrivito into your Rails application, the information provided by a content creator for individual products can be stored in CMS objects. So, let's create a CMS object class, ProductData, for such objects:

Copy
class ProductData < Obj
  attribute :body, :widgetlist
  attribute :product_id, :string
end

This new CMS object class has a product_id attribute that lets you retrieve the product associated with a given CMS object. We'll set the value of this attribute as ProductData instances are created.

We are going to let content editors add product data to a product, via the view the shop application provides for displaying products. Since the editors will do this using working copies, we might end up with more than one ProductData object for a single product. To prevent this, we'll compute the ID of a new ProductData object from the product's ID using SHA1. Yes, you can set the ID of a CMS object programmatically, and since Scrivito ensures that object IDs are unique across working copies, only one ProductData object can be created for a product.

SHA1 is a hashing algorithm that converts a given input to a 40 character hexadecimal string. The same input always produces the same result. For example:

Copy
> Digest::SHA1.hexdigest('scrivito')
=> "cf655746ab8cb7e0981fe54ba45b6f8033dcd811"

Scrivito expects an object ID to be a 16 character hexadecimal string. So, we can just use SHA1 to translate a product ID to a hexadecimal string and then use the first 16 characters as the Scrivito object ID. Due to the properties of the hashing algorithm, it is safe to use a substring only:

Copy
  def self.scrivito_id_for_product(product)
    Digest::SHA1.hexdigest("product-#{product.id}")[0...16]
  end

Note that we prefix the product ID with "product-" prior to computing the object ID. This ensures that the ID remains unique even if the shop utilizes further data sources containing items whose IDs might be identical to product IDs.

With a translation from product to Scrivito ID in place, we can implement methods to create and find the ProductData belonging to a product:

Copy
  def self.create_for(product)
    create(_id: scrivito_id_for_product(product), product_id: product.id)
  end

  def self.find_for(product)
    find(scrivito_id_for_product(product))
  end

Displaying the CMS objects

First of all, we need to load the ProductData of the given product in the Controller. This can be done in a before_action callback:

Copy
class ProductsController < ApplicationController
  include Scrivito::ControllerHelper

  before_action :fetch_scrivito_object, only: [:show]
  
  []
  
  private
  
  def fetch_scrivito_object
    @obj =
      begin
        ProductData.find_for(@product)
      rescue Scrivito::ResourceNotFound
        # Nothing to do here
      end
  end
end

The Scrivito::ControllerHelper includes several useful helpers for working with Scrivito objects. The remaining code fetches the CMS object for the show action. Please note, if loading the object fails, the error is rescued, but no further action is taken. We need to handle this in the template.

So, let's have the Scrivito object displayed. Add the following lines to your show template:

Copy
<% if @obj %>
  <%= scrivito_tag :div, @obj, :body %>
<% end %>

This code renders the body of the CMS object if it exists. However, none of the products is associated with a CMS object yet. You can create the CMS objects manually in the Rails console or use a migration. The code for creating such an object looks something like this:

Copy
> Scrivito::Workspace.current = Scrivito::Workspace.create(title: 'Add Product Data')
> product = Product.find(1)
> obj = ProductData.create_for(product)

Allowing editors to create product data

Creating CMS objects using the console is fine for developers, but what about the content creators? They need to be able to create ProductData objects on their own. For this, we are going to provide a link in the view that lets content editors create a new ProductData object if the given product has none. First of all, let's build the controller method:

Copy
class ProductsController < ApplicationController
  [...]
  
  def create_scrivito_obj
    ProductData.create_for(@product)
    redirect_to product_path(@product)
  end
  
  [...]
end

You will also need to make this new method available in the routes file. For example, with REST routing:

Copy
Rails.application.routes.draw do
  [...]
  resources :products do
    member do
      post 'create_scrivito_obj'
    end
  end
  
  [...]
end

Finally, we want a link for triggering the new action to appear in the view. Change the template code that renders the CMS object to:

Copy
<% if @obj %>
  <%= scrivito_tag :div, @obj, :body %>
<% elsif scrivito_in_editable_view? %>
  <%= link_to "Provide product data", create_scrivito_obj_product_path(@product) ,method: 'POST'%>
<% end %>

This code now displays the CMS object if it is available. If not, and if the current user has write access to the working copy, a link for creating the ProductData object is rendered.

Viewing ProductData objects directly

Usually, editors open the changes list of a working copy to get an overview of the changes they made or to review a particular page. Of course, ProductData objects also show up in the changes list, however, selecting such an object would not have the desired result because it cannot be displayed individually. ProductData objects are meant to be displayed in conjunction with the product they are associated with. We've taken care of this above by rendering the objects in the product view.

In order to have the associated product displayed after selecting a ProductData object from the changes list or opening its URL using a browser, we can use a redirection in the ProductDataController:

Copy
class ProductDataController < CmsController
  def index
    redirect_to product_path(@obj.product)
  end
end

Whenever a visitor wants to view a ProductData item, the code above redirects them to the product page the ProductData belongs to.

We haven't defined the product instance method yet. It simply looks up the product ID that was stored in the ProductData object on its creation.

Copy
class ProductData < Obj
  [...]

  def product
    Product.find(product_id)
  end

  [...]
end

You should now be able to integrate Scrivito and utilize its in-place editing power with any existing Rails application that uses its own data source and views for rendering the data.