Overview
This integration guide shows you how to how to update your existing project to:- install the ButterCMS package
- instantiate ButterCMS
- 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
- Bundler
- Gem Install
Add to your Then run:
Gemfile:gem 'buttercms-ruby'
bundle install
gem install buttercms-ruby
Initialize the client
Create an initializer:# config/initializers/buttercms.rb
ButterCMS::api_token = ENV['BUTTERCMS_API_TOKEN']
# .env
BUTTERCMS_API_TOKEN=your_api_token
For complete SDK documentation including all available methods and configuration options, see the Ruby SDK Reference.
Service object (recommended)
- Service Class
- Direct Usage
# 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
# Direct SDK usage in controllers
class PagesController < ApplicationController
def show
@page = ButterCMS::Page.find('landing-page', params[:slug] || 'home')
rescue ButterCMS::NotFound
raise ActionController::RoutingError.new('Not Found')
end
end
Pages
- Service Pattern
- Direct Usage
# 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/controllers/pages_controller.rb
class PagesController < ApplicationController
def show
slug = params[:slug] || 'home'
begin
@page = ButterCMS::Page.find('landing-page', slug, { locale: I18n.locale })
rescue ButterCMS::NotFound
raise ActionController::RoutingError.new('Not Found')
end
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
- Service Pattern
- Direct Usage
# app/controllers/brands_controller.rb
class BrandsController < ApplicationController
def index
content = ButterService.get_collection('brands')
@brands = content[:brands]
end
end
# app/controllers/brands_controller.rb
class BrandsController < ApplicationController
def index
content = ButterCMS::Content.fetch(['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
- Service Pattern
- Direct Usage
# 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/controllers/landing_controller.rb
class LandingController < ApplicationController
def show
begin
@page = ButterCMS::Page.find('landing-page', params[:slug])
@components = @page.fields[:body] || []
rescue ButterCMS::NotFound
raise ActionController::RoutingError.new('Not Found')
end
end
end
<%# app/views/landing/show.html.erb %>
<% content_for :title, @page.fields[:seo]&.[](:title) || 'Page' %>
<main>
<%= render_butter_components(@components) %>
</main>
Blog
- Blog List
- Blog Post
- Service Pattern
- Direct Usage
# 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/controllers/blog_controller.rb
class BlogController < ApplicationController
def index
response = ButterCMS::Post.all(
page: params[:page] || 1,
page_size: 10
)
@posts = response[:data]
@meta = response[:meta]
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>
- Service Pattern
- Direct Usage
# app/controllers/blog_controller.rb
def show
@post = ButterService.get_post(params[:slug])
raise ActionController::RoutingError.new('Not Found') unless @post
end
# app/controllers/blog_controller.rb
def show
begin
@post = ButterCMS::Post.find(params[:slug])
rescue ButterCMS::NotFound
raise ActionController::RoutingError.new('Not Found')
end
end
<%# app/views/blog/show.html.erb %>
<% content_for :title, @post[:seo_title] || @post[:title] %>
<% content_for :description, @post[:meta_description] || @post[:summary] %>
<article>
<h1><%= @post[:title] %></h1>
<p class="meta">
By <%= @post[:author][:first_name] %> <%= @post[:author][:last_name] %>
on <%= Date.parse(@post[:published]).strftime('%B %d, %Y') %>
</p>
<% if @post[:featured_image] %>
<%= image_tag @post[:featured_image], alt: @post[:title] %>
<% end %>
<%= raw @post[:body] %>
<% if @post[:categories]&.any? %>
<div class="categories">
Categories:
<% @post[:categories].each do |category| %>
<%= link_to category[:name], blog_category_path(category[:slug]) %>
<% end %>
</div>
<% end %>
<%= link_to 'Back to Posts', blog_path %>
</article>
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