Customizing Error Pages

This guide assumes that you are familiar with widgets. Customizing error pages will not be a pressing problem until you've developed a working application with Scrivito.

Every Ruby on Rails application includes customizable static error pages. When the app serves CMS content that can be edited in place, static error pages may suddenly seem like a relic. As a developer, you may wonder:

  • How do I make the content on error pages editable?
  • How do I localize the error pages when I have content in different languages?
  • How do I exclude those error pages from the navigation?

We will tackle all these questions in this guide.

Making error pages editable

Let's start by making the content of error pages editable. We will first create a new type of CMS object, “ErrorPage”:

Copy
$ bin/rails g scrivito:page ErrorPage
      create  app/models/error_page.rb
      create  app/controllers/error_page_controller.rb
      create  app/views/error_page/index.html.erb
      create  app/views/error_page/details.html.erb
      create  app/views/error_page/thumbnail.html.erb

Open the generated model file and define or redefine the attributes of the “ErrorPage” class:

Copy
# app/models/error_page.rb
class ErrorPage < Obj
  attribute :title, :string
  attribute :body, :widgetlist
end

For the sake of simplicity, the errors will only have the title and body attributes. Feel free to add further attributes as you see fit.

Next, provide the “Homepage” class with an attribute for storing the actual error page (which doesn't exist yet). This makes it easier later on to access the error page.

Copy
# app/models/homepage.rb
class Homepage < Obj
  # …
  attribute :error_not_found_page, :reference
end

Now, on the Rails console, let's create the error page and store it in the reference attribute of the actual homepage:

Copy
$ homepage = Obj.root

$ error_not_found_page = ErrorPage.create(
  _path: "#{homepage.path}/_global/error-not-found", title: 'Page not found')

$ homepage.update(error_not_found_page: error_not_found_page.id)

Editors will be able to add additional error pages to the website. To prevent this, disable the object class after creating the actual page by calling the hide_from_editor method in the model class.

Since the error page now is a regular CMS object, and we've anchored it in the homepage object, you can now access it like this:

Copy
$ bin/rails r 'puts Obj.root.error_not_found_page.id'
a46745...

When you start your application and visit localhost:3000/a46745... (using the ID you found above), you can marvel at your new "Page not found" page. Go ahead and edit the title or add widgets!

So, now you created a new page and are able to edit its content, but your application still serves the default error message Could not find Obj with permalink 'missing' if you visit localhost:3000/missing.

Thus, our last step is to connect the dots by rescuing the error and serving the new error page in case of a 404:

Copy
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

  unless config.consider_all_requests_local
    rescue_from(Scrivito::ResourceNotFound, with: :not_found)
  end

  private

  def not_found
    respond_to do |type|
      type.html do
        @obj = Obj.root.try(:error_not_found_page)

        if @obj.present?
          template = "#{@obj.controller_name.underscore}/#{@obj.controller_action_name}"
          render(template: template, status: 404)
        else
          render(file: 'public/404', layout: false, status: 404)
        end
      end
      type.all do
        render(nothing: true, status: 404)
      end
    end
  end
end

Whenever your application now gets a request for a missing page, your editable error_not_found_page is shown (except in RAILS_ENV=development). You can even edit this page in place using any URL that responds with HTTP 404 "Not Found", provided you have editing enabled.

Localizing error pages

To use error pages that are localized for different country sites, you'll probably have individual homepages for each country.

In this case, you can add several error page objects and reference a dedicated error page on the error_not_found_page of every homepage. You then need to modify the not_found method so it assigns the corresponding @obj.

Excluding error pages from navigations

A navigation in Scrivito is usually built by traversing a content tree, starting at the parent (e.g. the homepage).

By definition, pages without a parent cannot be part of a navigation. There is no validation for the path attribute, so you can easily create a page that has no parent. Have you noticed above how we used #{homepage.path}/_global/error-not-found as the path? As long as no object with the path #{homepage.path}/_global is created, the error page will not be included in any navigation.