Mini-CMS for your Rails application
I’ve worked on several applications where an end user may need to change text on various sections of a web site. I’ve used this technique a few times and it’s worked out well so I thought I’d share.
We’re going to create a model called Content which will be backed by a database. When a request comes in, we can look at the requested controller and action name to look up all of the content sections for a page. Some pages may have more than one content section.
This solution is intended to be implemented and modified by developers. An end user would not be able to add new content regions to a system.
Create the model and table
ruby script/generate model Content
Next, modify the migration:
class CreateContents < ActiveRecord::Migration
def self.up
create_table :contents do |t|
t.string :controller, :action, :name
t.text :body
t.timestamps
end
end
def self.down
drop_table :contents
end
end
The name field is what our end user will use to locate and edit a section. They don't need to know the controller and action name. Instead, they'll probably look for a content region like "Home Page main content". In order to make this work, the user will only be able to edit the body field. The other fields will be handled by the system.
Now, open up the content model. We're going to add a few methods here to easily grab the content out so we can use it in views. Out of the box, ActiveRecord allows us to find all the sections for a controller and action name by doing
@contents = Content.find_all_by_controller_and_action("public", "index")
At first glance, that looks pretty cool, but how do you identify the individual regions? You'd have to loop through that collection every time you want to output something. If you only have one content area per page, this could be good enough. However, I like things to be a little more spelled-out.
Add this method to the content class:
def self.get_content_for(controller, action)
c = Content.find_all_by_controller_and_action(controller, action)
content = Hash.new
c.each do |record|
content[record.name] = record.body
end
content
end'
Now, to fetch all of the sections for the page, we only need to call
@contents = Content.get_content_for "public", "index"
This class method fetches all of the content sections and then puts then into a hash which you can afccess via the name.
Trying it out
Open up the console and put two content regions in:
ruby script/console
Content.create :controller=>"public", :action=>"index", :name=>"intro", :body => "Hello world"
Content.create :controller=>"public", :action=>"index", :name=>"main_content", :body => "Main content goes here"
Now quickly generate a new controller and action
ruby script/generate controller public index
Add this to the public controller
before_filter :get_content
Now open application.rb and add this method:
def get_content
@content = Content.get_content_for(params[:controller], params[:action])
end
Finally, open up app/views/public/index.html.erb and change its contents.
Home page
<%=@contents["intro"] %>
About us
<%=@contents["main_content"] %>
Summary
I've used this approach in a few sites and it's served me well. I'd love to hear how you've done the same. Post ideas and suggestions in the comments.
Thanks simple & elegant