Add a CMS to Ruby on Rails in 5 minutes

Contents
    Try Storyblok

    Storyblok is the first headless CMS that works for developers & marketers alike.

    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.

    Resource Link
    Github repository of this Tutorial github.com/storyblok/rails-boilerplate
    Ruby on Rails Ruby on Rails
    Storyblok App Storyblok