Skip to main content

Overview

This integration guide shows you how to how to update your existing project to:
  1. install the ButterCMS package
  2. instantiate ButterCMS
  3. create components to fetch and display each of the three ButterCMS content types: Pages, Collections, and Blog Posts.
In order for the snippets to work, you’ll need to setup your dashboard content schemas inside of ButterCMS first.

Starter project

Or, you can jump directly to the starter project below, which will allow you to clone, install, run, and deploy a fully working starter project that’s integrated with content already inside of your ButterCMS account.

Rails Starter Project

Hit the ground running with a pre-configured Rails + ButterCMS setup.

Installation

Add to your Gemfile:
gem 'buttercms-ruby'
Then run:
bundle install

Initialize the client

Create an initializer:
# config/initializers/buttercms.rb
ButterCMS::api_token = ENV['BUTTERCMS_API_TOKEN']
Add your API token to your environment:
# .env
BUTTERCMS_API_TOKEN=your_api_token
For complete SDK documentation including all available methods and configuration options, see the Ruby SDK Reference.
# app/services/butter_service.rb
class ButterService
  class << self
    def get_page(page_type, slug, params = {})
      ButterCMS::Page.find(page_type, slug, params)
    rescue ButterCMS::NotFound
      nil
    end

    def get_pages(page_type, params = {})
      ButterCMS::Page.list(page_type, params)
    end

    def get_collection(*keys, **params)
      ButterCMS::Content.fetch(keys.flatten, params)
    end

    def get_posts(params = {})
      defaults = { page: 1, page_size: 10 }
      ButterCMS::Post.all(defaults.merge(params))
    end

    def get_post(slug)
      ButterCMS::Post.find(slug)
    rescue ButterCMS::NotFound
      nil
    end

    def search_posts(query, params = {})
      ButterCMS::Post.search(query, params)
    end

    def get_categories(params = {})
      ButterCMS::Category.all(params)
    end

    def get_tags(params = {})
      ButterCMS::Tag.all(params)
    end
  end
end

Pages

# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def show
    slug = params[:slug] || 'home'
    @page = ButterService.get_page('landing-page', slug, { locale: I18n.locale })

    raise ActionController::RoutingError.new('Not Found') unless @page
  end

  def index
    response = ButterService.get_pages('landing-page')
    @pages = response[:data]
  end
end
<%# app/views/pages/show.html.erb %>
<% content_for :title, @page.fields[:seo]&.[](:title) || @page.fields[:headline] %>
<% content_for :description, @page.fields[:seo]&.[](:description) || '' %>

<main>
  <h1><%= @page.fields[:headline] %></h1>
  <p><%= @page.fields[:subheadline] %></p>

  <% if @page.fields[:hero_image] %>
    <%= image_tag @page.fields[:hero_image], alt: @page.fields[:headline] %>
  <% end %>

  <%= raw @page.fields[:body] %>
</main>

Collections

# app/controllers/brands_controller.rb
class BrandsController < ApplicationController
  def index
    content = ButterService.get_collection('brands')
    @brands = content[:brands]
  end
end
<%# app/views/brands/index.html.erb %>
<main>
  <h1>Our Brands</h1>
  <ul>
    <% @brands.each do |brand| %>
      <li>
        <%= image_tag brand[:logo], alt: brand[:name] %>
        <h2><%= brand[:name] %></h2>
        <%= raw brand[:description] %>
      </li>
    <% end %>
  </ul>
</main>

Dynamic components

Component renderer

# app/helpers/component_helper.rb
module ComponentHelper
  def render_butter_components(components)
    components.map { |component| render_component(component) }.join.html_safe
  end

  def render_component(component)
    type = component[:type]
    fields = component[:fields]

    case type
    when 'hero'
      render_hero(fields)
    when 'features'
      render_features(fields)
    when 'cta'
      render_cta(fields)
    else
      ''
    end
  end

  private

  def render_hero(fields)
    content_tag :section, class: 'hero' do
      html = content_tag(:h1, fields[:headline])
      html += content_tag(:p, fields[:subheadline])

      if fields[:button_label].present?
        html += link_to(fields[:button_label], fields[:button_url], class: 'btn')
      end

      if fields[:image].present?
        html += image_tag(fields[:image], alt: fields[:headline])
      end

      html
    end
  end

  def render_features(fields)
    content_tag :section, class: 'features' do
      html = content_tag(:h2, fields[:headline])
      html += content_tag(:div, class: 'features-grid') do
        fields[:items].map do |item|
          content_tag(:div, class: 'feature') do
            feature_html = ''
            feature_html += image_tag(item[:icon], alt: item[:title]) if item[:icon].present?
            feature_html += content_tag(:h3, item[:title])
            feature_html += content_tag(:p, item[:description])
            feature_html.html_safe
          end
        end.join.html_safe
      end
      html
    end
  end

  def render_cta(fields)
    content_tag :section, class: 'cta' do
      html = content_tag(:h2, fields[:headline])
      html += content_tag(:p, fields[:subheadline])
      html += link_to(fields[:button_label], fields[:button_url], class: 'btn')
      html
    end
  end
end

Example component partial

<%# app/views/components/_hero.html.erb %>
<section class="hero">
  <div class="hero-content">
    <h1><%= fields[:headline] %></h1>
    <p class="subheadline"><%= fields[:subheadline] %></p>
    <% if fields[:button_label].present? %>
      <%= link_to fields[:button_label], fields[:button_url], class: 'hero-cta' %>
    <% end %>
  </div>
  <% if fields[:image].present? %>
    <div class="hero-image">
      <%= image_tag fields[:image], alt: fields[:headline] %>
    </div>
  <% end %>
</section>

Using in pages

# app/controllers/landing_controller.rb
class LandingController < ApplicationController
  def show
    @page = ButterService.get_page('landing-page', params[:slug])

    raise ActionController::RoutingError.new('Not Found') unless @page

    @components = @page.fields[:body] || []
  end
end
<%# app/views/landing/show.html.erb %>
<% content_for :title, @page.fields[:seo]&.[](:title) || 'Page' %>

<main>
  <%= render_butter_components(@components) %>
</main>

Blog

# app/controllers/blog_controller.rb
class BlogController < ApplicationController
  def index
    response = ButterService.get_posts(
      page: params[:page] || 1,
      page_size: 10
    )
    @posts = response[:data]
    @meta = response[:meta]
  end

  def category
    response = ButterService.get_posts(
      category_slug: params[:slug],
      page: params[:page] || 1,
      page_size: 10
    )
    @posts = response[:data]
    @meta = response[:meta]
    render :index
  end
end
<%# app/views/blog/index.html.erb %>
<main>
  <h1>Blog</h1>
  <ul>
    <% @posts.each do |post| %>
      <li>
        <h2><%= link_to post[:title], blog_post_path(post[:slug]) %></h2>
        <p class="meta">
          By <%= post[:author][:first_name] %> <%= post[:author][:last_name] %>
          on <%= Date.parse(post[:published]).strftime('%B %d, %Y') %>
        </p>
        <p><%= post[:summary] %></p>
      </li>
    <% end %>
  </ul>

  <% if @meta[:next_page] %>
    <%= link_to 'Next Page', blog_path(page: @meta[:next_page]) %>
  <% end %>
</main>

Routes

# config/routes.rb
Rails.application.routes.draw do
  # Pages
  root 'pages#show'
  get 'page/:slug', to: 'pages#show', as: 'page'
  get 'landing/:slug', to: 'landing#show', as: 'landing'

  # Collections
  get 'brands', to: 'brands#index'

  # Blog
  get 'blog', to: 'blog#index'
  get 'blog/category/:slug', to: 'blog#category', as: 'blog_category'
  get 'blog/:slug', to: 'blog#show', as: 'blog_post'

  # Webhooks
  post 'webhooks/buttercms', to: 'webhooks#buttercms'
end

Preview mode

# app/controllers/concerns/butter_previewable.rb
module ButterPreviewable
  extend ActiveSupport::Concern

  private

  def preview_mode?
    params[:preview] == 'true'
  end

  def page_params
    preview_mode? ? { preview: 1 } : {}
  end
end

# Usage in controller
class PagesController < ApplicationController
  include ButterPreviewable

  def show
    slug = params[:slug] || 'home'
    @page = ButterService.get_page('landing-page', slug, page_params)
    @is_preview = preview_mode?

    raise ActionController::RoutingError.new('Not Found') unless @page
  end
end

Caching

# app/controllers/concerns/butter_cacheable.rb
module ButterCacheable
  extend ActiveSupport::Concern

  def cached_page(page_type, slug, preview: false)
    return ButterService.get_page(page_type, slug, { preview: 1 }) if preview

    Rails.cache.fetch("butter_page_#{page_type}_#{slug}", expires_in: 1.hour) do
      ButterService.get_page(page_type, slug)
    end
  end

  def cached_posts(params = {})
    cache_key = "butter_posts_#{params.to_param}"
    Rails.cache.fetch(cache_key, expires_in: 1.hour) do
      ButterService.get_posts(params)
    end
  end

  def cached_collection(*keys)
    cache_key = "butter_collection_#{keys.join('_')}"
    Rails.cache.fetch(cache_key, expires_in: 1.hour) do
      ButterService.get_collection(*keys)
    end
  end

  def invalidate_butter_cache(pattern = 'butter_*')
    Rails.cache.delete_matched(pattern)
  end
end

Webhook cache invalidation

# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def buttercms
    event = params.dig(:webhook, :event) || ''

    if event.include?('page')
      Rails.cache.delete_matched('butter_page_*')
    end

    if event.include?('post')
      Rails.cache.delete_matched('butter_posts_*')
    end

    if event.include?('content')
      Rails.cache.delete_matched('butter_collection_*')
    end

    head :ok
  end
end

SEO

<%# app/views/layouts/application.html.erb %>
<!DOCTYPE html>
<html>
<head>
  <title><%= content_for?(:title) ? yield(:title) : 'My Site' %></title>
  <meta name="description" content="<%= content_for?(:description) ? yield(:description) : '' %>">

  <!-- Open Graph -->
  <meta property="og:title" content="<%= content_for?(:og_title) ? yield(:og_title) : yield(:title) %>">
  <meta property="og:description" content="<%= content_for?(:og_description) ? yield(:og_description) : yield(:description) %>">
  <% if content_for?(:og_image) %>
    <meta property="og:image" content="<%= yield(:og_image) %>">
  <% end %>

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="<%= content_for?(:title) ? yield(:title) : 'My Site' %>">
  <meta name="twitter:description" content="<%= content_for?(:description) ? yield(:description) : '' %>">
</head>
<body>
  <%= yield %>
</body>
</html>
<%# app/views/pages/show.html.erb %>
<%
  seo = @page.fields[:seo] || {}
  headline = @page.fields[:headline]
%>

<% content_for :title, seo[:title] || headline %>
<% content_for :description, seo[:description] || '' %>
<% content_for :og_title, seo[:og_title] || seo[:title] || headline %>
<% content_for :og_description, seo[:og_description] || seo[:description] || '' %>
<% content_for :og_image, seo[:og_image] if seo[:og_image].present? %>

<main>
  <!-- Page content -->
</main>

Resources

Rails Starter

Pre-configured starter project

Ruby SDK

Complete SDK reference

GitHub Repository

View source code

Webhooks

Set up content webhooks