Better Controllers with finder_filter and from_param
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:
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.
- Write as little code as possible
- 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.