5 Comments

A Lightweight “CMS” Using Ruby and Google Drive

My current project includes a fairly large amount of static text written by our customer’s marketing team. The authors generally do not have the background to provide valid HTML or contribute changes directly to our codebase.

Since there is a large amount of this text and we anticipate frequent updates throughout the course of the project, we were looking for an automated process we could use to import this text, convert it to valid HTML, and create the appropriate templates in our project’s file hierarchy. While there are plenty of full-featured Content Management System (CMS) options out there, we wanted something very lightweight that could be easily integrated into our existing project and would be fairly painless for our customers to use.

We created a simplified “CMS” system with two steps:

  1. Content authors enter text in a Google Drive spreadsheet we created. (Here’s an example.)
  2. We use a Ruby script to grab the text from the spreadsheet and generate the appropriate templates in our application.

The Spreadsheet

The spreadsheet actually includes a series of sheets, one for each type of content we need (sidebars, posts, dashboards, etc.). Headings explain what kind of content should be entered in each column, and we use the ID column to distinguish between valid content rows and rows that are simply separators, titles, or comments.

The text won’t be heavily formatted — we’re just using headings, bulleted lists, and the occasional link or line break. The authors will be using Textile markup to note what the format should be, providing text like:

h2. Some content for a template!
* Interesting thing #1
* Interesting thing #2

The Code

Then, using the google_drive Ruby gem, we can grab the live content from the spreadsheet and generate the appropriate templates in our application. Here’s a simplified version of what we do (note that this script requires Ruby version 1.9.3):

require 'pathname'   
require 'redcloth'
require 'google_drive'
require 'highline/import'
 
ROOT = Pathname(File.dirname __FILE__)
VIEWS_ROOT = ROOT + "Views"
VIEWS_ROOT.mkdir unless VIEWS_ROOT.exist?
FILES_ROOT = ROOT + "Files"
FILES_ROOT.mkdir unless FILES_ROOT.exist?
 
def get_password(prompt="Enter password")
   ask(prompt) {|q| q.echo = false}
end
 
def get_username(prompt="Enter Drive username")
   ask(prompt)
end
 
def generate_template(id, text, dirName)
  return unless text and !text.strip.empty?
  templateDir = VIEWS_ROOT + dirName
  templateDir.mkdir unless templateDir.exist?
  path = templateDir + (id + ".ascx")
  path.open "w" do |io|
    # We use the RedCloth library to generate html from textile markup
    io.puts RedCloth.new(text).to_html
  end
end
 
def generate_templates(spreadsheet, name)
  worksheet = spreadsheet.worksheet_by_title(name)
  # "worksheet.list" by default assumes the firt row to be keys for the columns. 
  # You can specify your own set of keys for the row though if you want to use
  # something else.
  worksheet.list.each {|row|
    rowId = row["Id"]
    # Insert logic here to tell if this is a valid row or not - we have a yaml
    # map that we check against to see if the given Id is valid for this type.
    generate_template(rowId, row["Text"], name) 
  }
end
 
# Establish a Drive session
session = GoogleDrive.login(get_username, get_password)
# Open the spreadsheet with the given name
spreadsheet = session.spreadsheet_by_title("drive_cms_example")
# Generate templates for all rows in the Posts worksheet
generate_templates(spreadsheet, "Posts")
# Generate templates for all rows in the Sidebars worksheet
generate_templates(spreadsheet, "Sidebars")
 
# Open the collection with the given name
collection = session.collection_by_title("Static Content")
# Download all files in the collection
collection.files.each {|file|
  file.download_to_file(FILES_ROOT + file.title)
}

Notice that the script also looks for a collection of files and downloads its contents. You could use this as a mechanism to update PDF documents, images, or whatever.

You can grab the needed gems via:

sudo gem install google_drive
sudo gem install RedCloth
sudo gem install highline

And should see output (depending on your Drive content) somewhat like:

.
|____Files
| |____Receipts.jpeg
| |____Tracking.jpeg
|____Views
| |____Posts
| | |____Another.ascx
| | |____One.ascx
| | |____Two.ascx
| |____Sidebars
| | |____Four.ascx
| | |____Three.ascx

Is it Awesome?

One concern was whether our authors would enjoy writing in the Textile format and using a spreadsheet for content entry. So far, we’ve received positive feedback.  We expect we may need some tooling to help authors validate their textile input and we may need to do some error-checking in our scripts.

As with most lightweight approaches, you may have requirements that make this a poor fit. It is definitely going to be nice for our team though to just run a script to inject all customer-provided copy into our application. We expect to use this script as part of our build process, but you could certainly implement an admin function to run a similar script live if you have a need to continue pulling in content after build/deployment.

We hope to save a lot of time with this approach. Let us know if you find it helpful or have suggestions for other lightweight methods to integrate customer content.