I've been nibbling away at how to write the code in Rails controllers recently, and I'm at a point where I'm fairly happy with what I'm doing. The two design requirements for me were:

  1. Write as little code as possible

  2. Get decent SEO-friendly URLs


Rails already supports friendly URLs, sort of, with to_param. Consider the canonical Post model from a blogging application:

[sourcecode language='ruby']
class Post < ActiveRecord::Base
def to_param
"#{id}-#{title}".gsub(" ","-").downcase.gsub(/[^a-z0-9-]/,"").gsub(/(-)*$/,"")
end
end
[/sourcecode]

Armed with that definition, it's possible to write a controller that works with URLs like /blogs/6-my-latest-blog-entry:

[sourcecode language='ruby']
class PostsController < ApplicationController

def index
@posts = Post.find(:all)
end

def show
@post = Post.find(params[:id])
end

def new
@post = Post.new
end

def edit
@post = Post.find(params[:id])
end

def create
@post = Post.new(params[:post])
if @post.save
flash[:notice] = 'Post was successfully created.'
redirect_to(@post)
else
render :action => "new"
end
end

def update
@post = Post.find(params[:id])

if @post.update_attributes(params[:post])
flash[:notice] = 'Post was successfully updated.'
redirect_to(@post)
else
render :action => "edit"
end
end

def destroy
@post = Post.find(params[:id])
@post.destroy
redirect_to(posts_url)
end
end
[/sourcecode]

OK, works fine. But there are a couple of things that annoy me. First is repeating the code to turn things into a slug in every model (though, with Inflector.paramterize coming in 2.2, this is going to get easier). Second is having to include a numeric ID at the start of the parameter. Third is those repeated calls to find(params[:id]).

Enter a couple of plug-ins: finder_filter, which gets rid of some of the duplication, and from_param, which handles the URLs a bit better. With those installed, I can rewrite the code this way:

[sourcecode language='ruby']
class Post < ActiveRecord::Base
def to_param
title.urlize
end
end

class PostsController < ApplicationController
finder_filter :only => [:show, :edit, :update, :destroy]

def index
@posts = Post.find(:all)
end

def show
end

def new
@post = Post.new
end

def edit
end

def create
@post = Post.new(params[:post])
if @post.save
flash[:notice] = 'Post was successfully created.'
redirect_to(@post)
else
render :action => "new"
end
end

def update
if @post.update_attributes(params[:post])
flash[:notice] = 'Post was successfully updated.'
redirect_to(@post)
else
render :action => "edit"
end
end

def destroy
@post.destroy
redirect_to(posts_url)
end
end
[/sourcecode]

Perfect? Naw, not really. But to my eyes, better. And getting better a little bit at a time is good enough for me.

The originals of those two plugins are not mine: finder_filter comes from Matthew Bass, and from_param comes from Michael Bleigh. But I've tweaked them both to work better together, which is why I linked to my forks of the projects.