webhouse.appwebhouse.appdocs

Read @webhouse/cms content from a Ruby on Rails application. Helper module, controllers, ERB views.

Setup

my-project/
  cms.config.ts
  content/
  public/uploads/
  app/
    controllers/
    views/
    helpers/

Helper module

Create app/lib/webhouse.rb:

ruby
require 'json'

module Webhouse
  CONTENT_DIR = Rails.root.join('content').freeze

  def self.collection(name, locale: nil)
    folder = CONTENT_DIR.join(name.to_s)
    return [] unless Dir.exist?(folder)

    Dir.glob(folder.join('*.json'))
       .map { |f| JSON.parse(File.read(f)) }
       .select { |d| d['status'] == 'published' }
       .then { |docs| locale ? docs.select { |d| d['locale'] == locale } : docs }
       .sort_by { |d| d.dig('data', 'date') || '' }
       .reverse
  end

  def self.document(collection, slug)
    path = CONTENT_DIR.join(collection.to_s, "#{slug}.json")
    return nil unless File.exist?(path)
    doc = JSON.parse(File.read(path))
    doc['status'] == 'published' ? doc : nil
  end

  def self.find_translation(doc, collection)
    tg = doc['translationGroup']
    return nil unless tg
    collection(collection).find do |other|
      other['translationGroup'] == tg && other['locale'] != doc['locale']
    end
  end
end

Controller

ruby
# app/controllers/blog_controller.rb
class BlogController < ApplicationController
  def index
    @posts = Webhouse.collection('posts', locale: 'en')
  end

  def show
    @post = Webhouse.document('posts', params[:slug])
    raise ActionController::RoutingError, 'Not Found' unless @post
  end
end

Routes

ruby
# config/routes.rb
Rails.application.routes.draw do
  root 'blog#index'
  get '/blog/:slug', to: 'blog#show', as: 'post'
end

ERB view

erb
<%# app/views/blog/show.html.erb %>
<article>
  <h1><%= @post.dig('data', 'title') %></h1>
  <time><%= @post.dig('data', 'date') %></time>
  <div class="prose">
    <%= markdown(@post.dig('data', 'content') || '') %>
  </div>
  <% (@post.dig('data', 'tags') || []).each do |tag| %>
    <%= link_to "##{tag}", "/tags/#{tag}", class: 'tag' %>
  <% end %>
</article>

Add a markdown helper in app/helpers/application_helper.rb:

ruby
require 'redcarpet'

module ApplicationHelper
  def markdown(text)
    renderer = Redcarpet::Render::HTML.new(hard_wrap: true)
    Redcarpet::Markdown.new(renderer, fenced_code_blocks: true).render(text).html_safe
  end
end

Add to Gemfile: gem 'redcarpet'

Serving media

Add public/uploads/ to the Rails public directory — it's served automatically by Rails in development and by your web server in production.

i18n

ruby
# Show a post with a language switcher
post = Webhouse.document('posts', params[:slug])
translation = Webhouse.find_translation(post, 'posts')

# In the view
if translation
  link_to "Read in #{translation['locale']}", blog_post_path(translation['slug'])
end

Caching

ruby
def self.collection_cached(name, locale: nil)
  Rails.cache.fetch("webhouse:#{name}:#{locale || 'all'}", expires_in: 1.minute) do
    collection(name, locale: locale)
  end
end

Jekyll alternative

For static Jekyll sites, read the same JSON files in a _plugins/webhouse.rb generator:

ruby
module Jekyll
  class WebhouseGenerator < Generator
    def generate(site)
      Dir.glob('content/posts/*.json').each do |f|
        doc = JSON.parse(File.read(f))
        next unless doc['status'] == 'published'
        site.pages << PageWithoutAFile.new(site, site.source, 'blog', "#{doc['slug']}.html").tap do |page|
          page.content = doc.dig('data', 'content')
          page.data['title'] = doc.dig('data', 'title')
          page.data['layout'] = 'post'
        end
      end
    end
  end
end

Next steps

Tags:FrameworksFilesystem Adapter
Previous
Consume from Django (Python)
Next
Consume from Go
JSON API →Edit on GitHub →