> ## Documentation Index
> Fetch the complete documentation index at: https://buttercms.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Rails

> Build content-driven Ruby on Rails applications with ButterCMS. Covers setup, pages, collections, components, and blog integration.

## 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](../../core-concepts/content-types/pages), [Collections](../../core-concepts/content-types/collections), and [Blog Posts](../../core-concepts/content-types/blog-engine).

<Tip>
  In order for the snippets to work, you'll need to [setup your dashboard content schemas inside of ButterCMS](../buttercms-setup) first.
</Tip>

### 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.

<Card title="Rails Starter Project" icon="rocket" href="../starter-projects/rails">
  Hit the ground running with a pre-configured Rails + ButterCMS setup.
</Card>

## Installation

<Tabs>
  <Tab title="Bundler">
    Add to your `Gemfile`:

    ```ruby theme={null}
    gem 'buttercms-ruby'
    ```

    Then run:

    ```bash theme={null}
    bundle install
    ```
  </Tab>

  <Tab title="Gem Install">
    ```bash theme={null}
    gem install buttercms-ruby
    ```
  </Tab>
</Tabs>

## Initialize the client

Create an initializer:

```ruby theme={null}
# config/initializers/buttercms.rb
ButterCMS::api_token = ENV['BUTTERCMS_API_TOKEN']
```

Add your API token to your environment:

```bash theme={null}
# .env
BUTTERCMS_API_TOKEN=your_api_token
```

<Info>
  For complete SDK documentation including all available methods and configuration options, see the [Ruby SDK Reference](../sdks/ruby-sdk).
</Info>

## Service object (recommended)

<Tabs>
  <Tab title="Service Class">
    ```ruby theme={null}
    # app/services/butter_service.rb
    class ButterService
      class << self
        def get_page(page_type, slug, params = {})
          ButterCMS::Page.get(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
    ```
  </Tab>

  <Tab title="Direct Usage">
    ```ruby theme={null}
    # Direct SDK usage in controllers
    class PagesController < ApplicationController
      def show
        @page = ButterCMS::Page.get('landing_page', params[:slug] || 'home')
      rescue ButterCMS::NotFound
        raise ActionController::RoutingError.new('Not Found')
      end
    end
    ```
  </Tab>
</Tabs>

## Pages

<Tabs>
  <Tab title="Service Pattern">
    ```ruby theme={null}
    # 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
    ```
  </Tab>

  <Tab title="Direct Usage">
    ```ruby theme={null}
    # app/controllers/pages_controller.rb
    class PagesController < ApplicationController
      def show
        slug = params[:slug] || 'home'

        begin
          @page = ButterCMS::Page.get('landing_page', slug, { locale: I18n.locale })
        rescue ButterCMS::NotFound
          raise ActionController::RoutingError.new('Not Found')
        end
      end
    end
    ```
  </Tab>
</Tabs>

```erb theme={null}
<%# 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

<Tabs>
  <Tab title="Service Pattern">
    ```ruby theme={null}
    # app/controllers/brands_controller.rb
    class BrandsController < ApplicationController
      def index
        content = ButterService.get_collection('brands')
        @brands = content[:brands]
      end
    end
    ```
  </Tab>

  <Tab title="Direct Usage">
    ```ruby theme={null}
    # app/controllers/brands_controller.rb
    class BrandsController < ApplicationController
      def index
        content = ButterCMS::Content.fetch(['brands'])
        @brands = content[:brands]
      end
    end
    ```
  </Tab>
</Tabs>

```erb theme={null}
<%# 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

```ruby theme={null}
# 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

```erb theme={null}
<%# 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

<Tabs>
  <Tab title="Service Pattern">
    ```ruby theme={null}
    # 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
    ```
  </Tab>

  <Tab title="Direct Usage">
    ```ruby theme={null}
    # app/controllers/landing_controller.rb
    class LandingController < ApplicationController
      def show
        begin
          @page = ButterCMS::Page.get('landing_page', params[:slug])
          @components = @page.fields[:body] || []
        rescue ButterCMS::NotFound
          raise ActionController::RoutingError.new('Not Found')
        end
      end
    end
    ```
  </Tab>
</Tabs>

```erb theme={null}
<%# app/views/landing/show.html.erb %>
<% content_for :title, @page.fields[:seo]&.[](:title) || 'Page' %>

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

## Blog

<Tabs>
  <Tab title="Blog List">
    <Tabs>
      <Tab title="Service Pattern">
        ```ruby theme={null}
        # 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
        ```
      </Tab>

      <Tab title="Direct Usage">
        ```ruby theme={null}
        # 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
        ```
      </Tab>
    </Tabs>

    ```erb theme={null}
    <%# 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>
    ```
  </Tab>

  <Tab title="Blog Post">
    <Tabs>
      <Tab title="Service Pattern">
        ```ruby theme={null}
        # app/controllers/blog_controller.rb
        def show
          @post = ButterService.get_post(params[:slug])

          raise ActionController::RoutingError.new('Not Found') unless @post
        end
        ```
      </Tab>

      <Tab title="Direct Usage">
        ```ruby theme={null}
        # 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
        ```
      </Tab>
    </Tabs>

    ```erb theme={null}
    <%# 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>
    ```
  </Tab>
</Tabs>

## Routes

```ruby theme={null}
# 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

```ruby theme={null}
# 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

```ruby theme={null}
# 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

```ruby theme={null}
# 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

```erb theme={null}
<%# 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>
```

```erb theme={null}
<%# 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

<CardGroup cols={2}>
  <Card title="Rails Starter" icon="rocket" href="../starter-projects/rails">
    Pre-configured starter project
  </Card>

  <Card title="Ruby SDK" icon="gem" href="../sdks/ruby-sdk">
    Complete SDK reference
  </Card>

  <Card title="GitHub Repository" icon="github" href="https://github.com/ButterCMS/buttercms-ruby">
    View source code
  </Card>

  <Card title="Webhooks" icon="bell" href="../../api/concepts/webhooks-overview">
    Set up content webhooks
  </Card>
</CardGroup>
