Part 2 of the articles on content management in Rails. This was originally written almost a year ago, when I was slightly dumber than I am now. Included for completeness, but I really want to take this in a different direction. I should pull my finger out but for now I’ll maintain the illusion of industriousness by throwing up old blog posts.
Continuing the general rant aimless musing about Rails as a CMS…
At work we use a single controller to serve up static content. The graphic designer on our team prefers to work with .html files in the file system so we use the static content controller to serve up web pages directly from disk. The files are ERB files – so ’.rhtml’ but close enough to be familiar to our designer. He also gets the bonus of all those helper methods, including custom helpers we build to make his life easier.
This approach has several advantages over keeping content in the database, like Radiant and others CMSs do. Firstly, publishing is just a matter of copying files into a known view directory. Secondly, we keep all content files under version control, so we can determine exactly which version of the content is to go live and which versions we can roll back to. We use branching to manage concurrent development of new content and maintenance of existing content.
We end up with the same model of development applying to both content and application development. The ancillary benefit is that everyone ends up talking the same language and content development ends up being managed with the same rigour that we apply to coding. This breaks down barriers in communication, allows us to work closely with designers and generally makes for a more enjoyable day’s work.
I want to use a similar approach with some documents I’m deploying as part of a web app. The documents aren’t going to change much but I don’t want to have to try to bundle them into the database using migrations. Apart from the duplication (the content would exist in both migration code and the database) I don’t want to have to build a new migration each time I make small changes to the text. Managing changes in a version control system is simpler so the static content controller approach suits here.
I also want to be to reuse this idea elsewhere, so I’m going to write the controller code as a plugin. If the code looks familiar, it’s because I nicked the original idea from here.
acts_as_static_content.rb
module ActsAsStaticContent
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_static_content
include ActsAsStaticContent::InstanceMethods
end
end
module InstanceMethods
def static
#SMELL: assumes the convention app/views/foo for FooController
if template_exists? path = "#{self.controller_name}/#{params[:id].to_s}"
render :template => path
elsif template_exists? path += "#{self.controller_name}/index"
render :template => path
else
# pass through to the global error page handler
method_missing params[:path].to_s
end
end
protected
def each_page
template_root = "#{RAILS_ROOT}/app/views/#{self.controller_name}"
excluded_actions = ['.', '..'].concat(self.hidden_actions)
Dir.foreach(template_root) do |action|
if !excluded_actions.include? action
template_path = "#{template_root}/#{action}"
yield action, template_path
end
end
end
end
end
ActionController::Base.send(:include, ActsAsStaticContent)
The module code is introduced into controllers like any acts_as_X feature.
class MyController < ApplicationController
acts_as_static_content
end
Any ERB files sitting in the /app/views/my folder will be available via the mixed in ‘static’ method. If we want this controller to serve up all static content by default we add this to the end of routes.rb:
map.connect '*id', :controller => 'my', :action=>'static'
Not much to it and as someone will ultimately point out you can serve up content like this with any controller methods – just drop any .rhtml file into a view folder and it behaves like an action anyway.
Putting aside any question of whether this sort of behaviour is actually desirable, the aim is to centralize the mechanism for managing content. The immediate value is in the ‘each_page’ method, as this can be used to add search features – not an explicit requirement of a CMS as per the previous post, but one my app will need and I suspect so will others.
I need to give the ‘static’ method a bit more smarts, but it’s a start. We’ve already ticked off one requirement – versioning – with a few more ready to be knocked down. This first cut will probably be unrecognizable after a few refactorings, but the only way to find out is to see what happens against a real app. So.. next post: eating my own dog food… through a straw.
