Filters¶
To achieve DRY code in controllers Rails provides feature called - filters. Filters are methods that get executed before, after or before and after code. Filters named after their behaviour: before_filter, after_filter, around_filter (which executes before and after code).
Before filters are often used for authentication, as you can see below. Lets take our controller and add before_filter:
class BooksController < ApplicationController
before_filter :login_required, :only => ["new", "create", "edit", "update"]
def index
revs = Review.all
@review1 = Review.find(revs.rand.id)
@review2 = Review.find(revs.rand.id)
@books = Book.all(:order => 'created_at DESC', :limit => 3)
end
def list
@books = Book.search(params[:search])
end
def show
@book = Book.find(params[:id])
@genres = @book.genres.map {|genre| genre.name}.compact.join(' , ')
@authors = @book.authors
if @current_user
@readings = @current_user.readings.map{|b| b.read }
end
end
def new
@book = Book.new
@authors = Author.all
@genres = Genre.all
respond_to do |wants|
wants.html
end
end
def create
@book = Book.new(params[:book])
@genres = Genre.all
@authors = Author.all
@book = @current_user.books.build params[:book]
respond_to do |wants|
if @book.save
wants.html do
flash[:notice] = "Successfully created book."
redirect_to @book
end
else
wants.html { render :action => 'new' }
end
end
end
def edit
@book = Book.find(params[:id])
@genres = Genre.all
@authors = Author.all
if @current_user.id == @book.user.id && @book.readers.length == 0
respond_to do |wants|
wants.html
end
else
flash[:error] = 'You cannot edit this book.'
redirect_to @book
end
end
def update
@book = Book.find(params[:id])
params[:book][:genre_ids] ||= []
params[:book][:author_ids] ||= []
respond_to do |wants|
if @book.update_attributes(params[:book])
wants.html do
flash[:notice] = "Successfully updated book."
redirect_to @book
end
else
wants.html { render :action => 'edit' }
end
end
end
def destroy
@book = Book.find(params[:id])
if @current_user.id == @book.user.id && @book.readers.length == 0
@book.destroy
flash[:notice] = "Successfully removed book."
redirect_to books_url
else
flash[:error] = "Cannot remove book."
redirect_to books_url
end
end
end
The login_required method is defined in the application_controller, but this method can be defined right in books_controller.
private
def login_required
return true if logged_in?
session[:return_to] = request.request_uri
redirect_to new_session_path and return false
end
It is better to put this code in the application_controller so you can use it in all your controllers.
Filters can have options :only and :except just as you saw in the Views_and_layouts section.
The line:
@book = Book.find(params[:id])is in four methods
show,edit, update, and destroy.
To remove this duplication, we can add another before filter to the book_controller:
private
def find_book
@book = Book.find(params[:id])
end
And then add the new before_filter to the top of controller:before_filter :login_required, :only => ["new", "create", "edit", "update"] before_filter :find_book, :only => ["show", "edit", "update", "destroy"]
So now we can delete line
@book = Book.find(params[:id])from top of actions: show, edit, update, destroy.
If you define a before_filter in application_controller, then that filter will execute for every controller. However, Rails contains method skip_defore_filter which allows you to bypass this functionality for a specific controller.
To use it, just enter :
skip_before_filter :filter_nameright after controller definition.
