Contents

Add a CMS to Ruby on Rails in 5 minutes

In this short article, we will show you how you can use the API-based CMS Storyblok in combination with the Framework “Ruby on Rails”. At the end of this article, you will have a Ruby on Rails application which renders components filled with data from Storyblok.

You can also clone the code of this tutorial at github.com/storyblok/rails-boilerplate.

1. Create a new Ruby on Rails project

You can add Storyblok to existing projects or new ones. I will show how to add Storyblok to a new project. Execute the following commands so you get a new project ready to start with:

console
rails new my-project
cd ./my-project

2. Add Storyblok’s Ruby SDK

Let’s install the headless CMS client and liquid as template language by adding following lines to our Gemfile.

Gemfile
gem 'liquid', github: 'Shopify/liquid', branch: 'master'
gem 'storyblok'

After adding the gems execute bundle install and start the rails app.

bundle install
bin/rails server -p 3000

Create a Storyblok space

After you have started your project you need to get the Storyblok preview token and tell Storyblok where your app is running. Create a new Storyblok space and click on the “Home” content item. Note down your preview token and insert localhost:3000 as your host.

Storyblok Bridge

3. Generate the pages controller

Storyblok is a page centric CMS so we create a controller that handles all requests with a wildcard route.

console
bin/rails g controller pages

Add the index method

Add following code to pages_controller.rb and exchange YOUR_TOKEN with the preview token from Storyblok’s settings page.

app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def index
    response.headers['X-FRAME-OPTIONS'] = 'ALLOWALL'

    client = Storyblok::Client.new(
      logger: logger,
      cache_version: Time.now.to_i,
      token: 'YOUR_TOKEN',
      version: 'draft'
    )

    assigns = {
      story: client.story(params[:path])['data']['story']
    }

    Liquid::Template.file_system = Liquid::LocalFileSystem.new('app/views/components')

    template = Liquid::Template.parse(File.read('app/views/layouts/page.liquid'))
    render html: template.render!(assigns.stringify_keys, {}).html_safe
  end
end

Extend the routes file

Add a wildcard route to route all requests to the pages controller.

config/routes.rb
Rails.application.routes.draw do
  match "*path", to: "pages#index", via: :all
end

4. Add the page template

Add the main page template page.liquid in layouts which includes the Storyblok Javascript bridge. Optionally you can add a conditional check for the parameter _storyblok in the url to include the Storyblok’s Javascript bridge only when the user is in the editing mode.

app/views/layouts/page.liquid
<!DOCTYPE html>
<html>
<head>
  <title>{{ story.name }}</title>

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
  <link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i" rel="stylesheet">
  <link rel="stylesheet" href="https://a.storyblok.com/t/43698/assets/css/above.css?1523887806">
</head>
<body>
  {% include 'header' with blok: global.content %}

  {% for blok in story.content.body %}
    {% include blok.component with blok: blok %}
  {% endfor %}

  <script type="text/javascript" src="//app.storyblok.com/f/storyblok-latest.js?t= Dv2ok3DqODzzb8QUuN2XCgtt"></script>
  <script type="text/javascript">

    storyblok.on('change', function(event) {
      if (!event.slugChanged) { location.reload(true) }
    })

    storyblok.on('published', function(event) {
      if (!event.slugChanged) { location.reload(true) }
    })

  </script>
</body>
</html>

5. Create the first editable components

The demo content that get’s created when adding a new space in Storyblok has already some components preconfigured. To make this components clickable in the editor the only thing that you need to do is to add {{ blok._editable }} before a DOM element.

Let’s create our first component templates _teaser.liquid, _grid.liquid and _feature.liquid in the components folder.

Create _teaser.liquid

app/views/components/_teaser.liquid
{{ blok._editable }}
<section class="fdb-block bg-dark fdb-viewport"
  style="background-image: url('//a.storyblok.com/f/43698/3000x2000/7696f16f6b/bg_c_1.svg')">
  <div class="container align-items-center justify-content-center d-flex">
    <div class="row justify-content-center">
      <div class="text-center col-12 col-sm-10 col-md-8">
        <h1>{{ blok.headline }}</h1>
        <p class="mt-5">
          <a class="btn " href="#">Call to action</a>
        </p>
      </div>
    </div>
  </div>
</section>

Create _grid.liquid

In this component we iterate over columns to include other components dynamically.

app/views/components/_grid.liquid
{{ blok._editable }}
<div class="container">
  <div class="row text-center justify-content-center pt-5 pb-5">
    {% for item in blok.columns %}
      {% include item.component with blok: item %}
    {% endfor %}
  </div>
</div>

Create _feature.liquid

app/views/components/_feature.liquid
{{ blok._editable }}
<div class="col-12 col-sm-6 col-lg-3 pt-4 pt-lg-0">
  <h3><strong>{{ blok.name }}</strong></h3>

  <p>{{ blok.text }}</p>
</div>

At the end you should be able to see a teaser and three feature blocks that are clickable in Storyblok.

Ruby on Rails CMS Screenshot

6. Create global components

To create global content like a header navigation you can define a new content type. Create a new “Story” name it “Global” and type setting in the content type field.

Global Ruby on Rails component

Add nav_links with type “Blocks” to the schema of the settings content type and then create nav_item blocks with name and link in the schema definition.

Global

Extend the pages_controller.rb

After you have published the global content item add the api call to your controller:

app/controllers/pages_controller.rb
...
assigns = {
  story: client.story(params[:path])['data']['story'],
  global: client.story('global')['data']['story']
}
...

Last step is to add the components to your Ruby on Rails project:

Add _header.liquid

app/views/components/_header.liquid
{{ blok._editable }}
<header>
  <div class="container">
    <nav class="navbar navbar-expand-md">
      <a class="navbar-brand" href="/">
      <img src="//a.storyblok.com/f/43698/437x202/371b07400b/img_logo_new.png" height="30" alt="Logo">
      </a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-2651eb25-213b-4216-8d83-354812817602" aria-controls="navbar-2651eb25-213b-4216-8d83-354812817602" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbar-2651eb25-213b-4216-8d83-354812817602">
        {% if blok.nav_links != blank %}
          <ul class="navbar-nav mr-auto">
            {% for item in blok.nav_links %}
              {% include item.component with blok: item %}
            {% endfor %}
          </ul>
        {% endif %}
      </div>
    </nav>
  </div>
</header>

Add _nav_item.liquid

app/views/components/_nav_item.liquid
{{ blok._editable }}
<li class="nav-item{% if blok.link.cached_url contains request.path %} active{% endif %}">
  <a class="nav-link" href="{{ blok.cached_url }}">
    {{ blok.name }}
  </a>
</li>

Conclusion

It’s incredibly easy to build a flexible block based website with Ruby and Rails and Storyblok. In case you want to extend an existing project with a blog be sure to checkout the how to create blog content structure article.

ResourceLink
Github repository of this Tutorialgithub.com/storyblok/rails-boilerplate
Ruby on RailsRuby on Rails
Storyblok AppStoryblok

More to read...

About the author

Alexander Feiglstorfer

Alexander Feiglstorfer

Passionate developer and always in search of the most effective way to resolve a problem. After working 13 years for agencies and SaaS companies using almost every CMS out there he founded Storyblok to solve the problem of being forced to a technology, proprietary template languages and the plugin hell of monolithic systems.