Posted by
ihower
on
July 24, 2010
Don't repeat yourself in controller, use before_filter to avoid duplicated codes.
Bad Smell
class PostsController < ApplicationController
def show
@post = current_user.posts.find(params[:id])
end
def edit
@post = current_user.posts.find(params[:id])
end
def update
@post = current_user.posts.find(params[:id])
@post.update_attributes(params[:post])
end
def destroy
@post = current_user.posts.find(params[:id])
@post.destroy
end
end
In this example, the first code in action show, edit, update and destroy are the same, we hate the duplicated code, use before_filter to avoid.
Refactor
class PostsController < ApplicationController
before_filter :find_post, :only => [:show, :edit, :update, :destroy]
def update
@post.update_attributes(params[:post])
end
def destroy
@post.destroy
end
protected
def find_post
@post = current_user.posts.find(params[:id])
end
end
As you see, all the post finders are removed from actions, and there is only one finder in the before_filter. Keep in mind that don't repeat yourself(DRY).

Comments
Furthermore before_filters are considered as basic bast practice of Rails... It's quite the equivalent of AOP in Java, and thus a very usefull way of transparently way of reusing code.
Use it and love it ! ;)
This is not to say I would never use it. I just wouldn't call it a best practice. With a LOT of actions I could see it being useful. But won't a controller with 14 actions need to be refactored anyway?
If it were more than a couple of lines of code then I'd probably do it. But for one line it's pointless.
DRY is a principle with the purpose to make code more manageable. When DRY abused, the code becomes esoteric -- less manageable. Let's avoid going overboard. No, at a minimum I want to see an assignment operator in my function if there is an assignment operation happening in my function.
It's useful and obvious that we should add a before_filter to find the parent @question for every actions in AnswersController
It can be helpful sometimes to reduce the amount of code and it is a good trick but should not be considered a best practice.
It won't throw an error if views for those actions exist.
@everyone disagreeing with this approach
I've been using this approach for over a year and a half, and it has worked very well for creating maintainable code. I migrated to this approach after using resources_controller for over a year. Now resources_controller was indeed obfuscating magic, but using before_filters isn't magic. It simply defines resource creation in a single location. In my experience, people don't have any problem understanding what is happening.
Using before filters to pull models into scope means that I can look at a single location and have a complete understanding of which actions find resources, and which create them. An :except block means that any new actions automatically find a resource (which is almost always what I need when adding actions). And this leaves actions to focus on the action, rather than pulling an object into scope and then performing the action on it. Here is an example for a resource that is nested within another resource:
before_filter :find_containing_resource
before_filter :new_resource, :only => [:new, :create]
before_filter :find_resource, :except => [:index, :new, :create]
before_filter :find_resources, :only => [:index]
...
private
def find_containing_resource
@post = Post.find(params[:post_id])
end
def new_resource
@comment = @post.comments.build(params[:comment])
end
def find_resource
@comment = @post.comments.find(params[:id])
end
def find_resources
@comments = @post.comments
end
The before_filter is the right thing to do if there's duplicated code. If you can't follow the logic flow -- i.e. if you think something this simple is "magic" -- then you should learn Rails better before trying to work with it.
I can't believe anyone's seriously arguing for repetitive code.
So the loading of the instance is still only written once. But:
1. It is obvious to anyone looking at the controller that an "edit" and "show" action exists (they may miss the fact that is is declared in the before filter).
2. When you look at each action it doesn't seem like the instance variables appear magically. You can clearly see something is ran before the instance variables are used. You can then do a search to find this "load_instance" method.
Obviously with our example my two points are not that big of a deal. But as the capabilities of a controller increases in complexity it becomes more difficult to see the "magic" of a before filter hidden somewhere in the app and the explicitness of method calling becomes very helpful.
Again, I am not saying loading data via before_filters is bad. Maybe for a real simple controller it is fine. Or maybe for a controller that has lots of similar non-RESTful actions it is also fine. But calling it a best practice is going too far. It is a technique that can be used in some situations. Nothing more.
I thought this was the most basic best practice out there for controllers, I guess I must be wrong.
You guys go ahead and waste time on duplicating your code in your REST controllers, I'll keep writing and get to working on harder more interesting problems...