8.8. Filters

Filters are a convenient way to wrap actions and execute code before and/or after an action has been, or is being, processed. Before filters are executed before the action processing takes place. If any of the filters in the before filter chain returns false, processing is terminated and no other filters (or the action itself) are executed for that request. Likewise, the code within after filters is executed after the code within the action has been processed.

The main filters are before_filter, after_filter, and around_filter. When any one of them appears within a controller, its effect applies to all the actions within the current controller and its subclasses. As you can probably imagine, placing a filter within ApplicationController adds that filter to the chain of filters for all the actions in the application. All three filters accept the :only and :except options — which you should be familiar with by now — to limit the number of actions for which the filter should be applied.

These methods accept a method name as a symbol:

class ExampleController < ActionController::Base
  before_filter :authenticate

  #...
  private

  def authenticate
    #...
 end
end

The method authenticate will be executed before any actions within ExampleController.

or a block:

class ExampleController < ActionController::Base
  after_filter do |controller|

# The action name is stored in controller.action_name
    #...
  end

  #...
end

In addition, they even accept any class (an object in the case of the around filter) that contains a definition for a method filter. That method needs to be a class method (for example, self.filter(controller,&block)) for before and after filters, and an instance method (that is, def filter(controller,&block)) for the around filter. For example:

class ExampleController < ActionController::Base
  before_filter LogFilter, :except => :download
  around_filter BenchmarkFilter.new, :only => :speed_test

  # ...
end

Every time before_filter or after_filter is defined in a controller, the filter that it defines is appended to its respective before or after filter chains. If, for instance, you were to define a before filter in a controller, and a before filter in its superclass, the filter within the controller would be executed only after the filter inherited from the parent class was executed (and only if this didn't return false). This may not be what you want in every scenario. To prefix, rather than append, a filter to its filter execution chain, you can use the corresponding methods prepend_before_filter and prepend_after_filter.

Unlike the other two filters, around_filter executes some code before the action is invoked, then the action is processed, and finally more code gets executed within the filter. As the name suggests, around filters wrap around an action. When passing a method name as a symbol, the action is invoked through yield:

class ExampleController < ActionController::Base
  around_filter :benchmark, :except => :login

  #...
  private

  def benchmark
    start = Time.now
    yield
    exec_time = Time.now - start
    logger.info("Processing #{action_name} took #{exec_time} seconds.")
  end
end

And as mentioned before, you also pass a block to the method. In this case, the method will have two block parameters, one for the controller and another for the action as a Proc object that's ready to be executed through the call method:

class ExampleController < ActionController::Base
  around_filter(:except => :login) do |controller, action|
    start = Time.now

action.call
    exec_time = Time.now - start
    logger.info("Processing #{controller.action_name} took #{exec_time} seconds."
  end
end

An around filter will typically invoke the execution of the action, but this is not necessarily the case. If yield (when passing a symbol to the filter declaration) or action.call (when passing to it a block) is not invoked, the action will not be executed.

NOTE

These examples are just a sample of how around_filter can be used; you shouldn't time your actions, because this information is already available in your logs.

When there are multiple declarations for around_filter, they're chained together by merging them, so that all the code before the execution of the action is executed in the order it appears, and all the code after the action is invoked will be executed after the action processing in the order that appears as well.

To clarify this further with an example, consider the following code that has three filters defined, in the order first_filter, second_filter, and then an unnamed filter that uses a block:

def ExampleController < ActionController::Base
  around_filter :first_filter, :second_filter

  around_filter do |controller, action|
    logger.info("In the block before the action")
    action.call
    logger.info("In the block after the action")
  end

  def index
    logger.info("In the index action")
  end

  private

  def first_filter
    logger.info("In the first filter before the action")
    yield
    logger.info("In the first filter after the action")
  end

  def second_filter
    logger.info("In the second filter before the action")
    yield
    logger.info("In the second filter after the action")
  end
end

When /my/index is invoked, the following result is yielded in the logs:

In the first filter before the action
In the second filter before the action

In the block before the action
In the index action
Rendering m/index
In the block after the action
In the second filter after the action
In the first filter after the action

It's also possible to skip the filters defined in a parent class from within children classes by invoking the methods skip_before_filter, skip_after_filter, and skip_filter (which skips any filter, including around filters). Declarations using these three methods accept only a method name as a symbol, and therefore can only skip filters defined through a symbol (and not those defined through a block or a class/object).

Just like filters, these declarations also accept :only and :except.

Filters are just regular code that's defined within the controller and as such they can access all the attributes and objects available to normal actions, including the ability to redirect and render content back to the user. For this reason, before filters are often used for authentication purposes and after filters are frequently delegated to the task of altering the response object in some way (for example, to compress the output when the user's browser supports it).

Verification

A higher abstraction layer based on filters is provided by Rails through the verify method. This method enables you to verify certain conditions before an action is executed. In practice, this is a fancier before_filter that's able to guard an action against the possibility of being invoked if certain conditions are not met. These conditions can verify the existence of a particular parameter key, flash key, session key, HTTP method, and whether it was an Ajax request (by passing :xhr).

Consult the documentation for examples and reference points for the method ActionController::Verification::ClassMethods.verify.


8.8.1. Using Filters for HTTP Basic Authentication

Having introduced the concept of filters, it is now easy to show how HTTP basic authentication can be added to the blog app. Rails provides a lengthy method called authenticate_or_request_with_http_basic, which enables you to log in through HTTP Basic Authentication, without the need to design an ad-hoc form. This is probably not something you'd want to expose your users to (usually), but as a blogger who needs to access a reserved area, you won't likely mind this. It's functional and extremely easy to implement. Moreover, it's a good example of how filters can aid in the design of Rails applications.

Open the ArticlesController and add the following:

class ArticlesController < ApplicationController
  before_filter :authenticate, :except => %w(index show)

  #... All the existing actions ...

private

  def authenticate
    authenticate_or_request_with_http_basic('Administration') do |username, password|
      username == 'admin' && password == 'secret'

   end
  end
end

Whenever an action on an article is requested, with the exception of index and show, which should be visible to the public, the application will try to authenticate the user. For example, try to click the Unpublished Articles link in the menu and you should be prompted for a username and a password as shown in Figure 8-2. Enter admin and secret, and you'll be able to log in and see the page. Clicking the Cancel button will produce an HTTP Basic: Access denied message (but you could just as easily create a redirect to a public page). When the user has already been authenticated, the login dialog will no longer be shown. However, unless the Remember Password check box has been marked off, or the browser remembers the credentials automatically for you, when you close your browser you are essentially "logging off" and will be prompted for credentials the next time you visit a protected link.

Figure 8.2. Figure 8-2

Notice that you get a warning about a basic authentication without a secure connection. The username and password will in fact be sent in clear text, so it's a very good idea to adopt SSL in production mode.

Now you need to protect a few actions in the CommentsController as well, because you don't want to allow your users to destroy or edit comments. Change it to look like this:

class CommentsController < ApplicationController
  layout "articles"

  before_filter :get_article
  before_filter :authenticate, :only => %w(edit update destroy)

  #... All the existing actions ...
  private

  def get_article
    @article = Article.find(params[:article_id])
  end

  def authenticate
    authenticate_or_request_with_http_basic('Administration') do |username, password|
      username == 'admin' && password == 'secret'

   end
  end
end

When you visit /articles/1/comments/1/edit now you are prompted for a username and password.

NOTE

Rails 2.3 adds a more secure form of HTTP Basic Authentication, known as HTTP Digest Authentication. This makes use of the authenticate_or_request_with_http_digest method.

8.8.2. Ideas for Improving the Authentication System

That username == 'admin' && password == 'secret' is extremely simplistic and that password in clear text might make you cringe. But the principle behind using before_filter along with HTTP Basic Authentication stands. You could, for example, replace that line of code with a User.authenticate(username, password) of sorts, and then implement authenticate as a class method in the model. Perhaps something like this:

def self.authenticate(username, password)
  user = self.find_by_username(username)
  user && user.valid_password?(password)
end

Where valid_password? is a method defined in the model, so that User instances are able to compare the password provided with the encrypted version stored in a field of the table.

As long as the method returns true when the user can be authenticated and false when the user cannot, authenticate_or_request_with_http_basic will be pleased and this basic logging system will work.

There is no User model in the sandbox/basic application, but many projects will have one.

Also note that the simplicity of the implementation shown in this example has a big downside: you show "admin" links to regular unauthenticated users. In some applications this might be okay, but for a blog this is not the case. To solve this problem, you could set an instance variable @is_authenticated within the authenticate method:

@is_authenticated = authenticate_or_request_with_http_basic('Administration') do |username,
password|
  username == 'admin' && password == 'secret'
end

And then you could check in the articles.html.erb layout if @is_authenticated, before visualizing admin links. You could even define a helper method within the ApplicationController:

class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time
  helper_method :logged_in? # Make logged_in? available in the view

  def logged_in?
    @is_authenticated
  end

  #...
end

With this in place, in the view it's possible to show a link only if the user is logged in, in this way:

<li><a href="/">Home</a></li>
<% if logged_in? -%>
  <li><%= link_to 'Unpublished Articles', unpublished_articles_path %></li>
  <li><%= link_to 'New Article', new_article_path %></li>
<% end -%>

The process will have to be repeated for every link that isn't supposed to appear to the unauthenticated user. Of course, if you were to do so, you'd be left with no link with which to trigger the login system when you, the blogger, aren't authenticated yet. In that case, you could either leave a single generic Admin link that points to a protected link, or simply bookmark a URL that triggers an action affected by the before_filter (for example, the path /articles/unpublished).

If you are experimenting with this, remember to assign the result of the authentication method to the @is_authenticated instance variable in the CommentsController as well, and hide links in the templates in appviewscomments too.

You may notice that having similar code in both controllers (articles and comments) is a minor infraction of the DRY principle. But nothing that can't be resolved with a bit of refactoring, for example, by placing the before_filter declaration and authentication method within an AdminController, subclass of ApplicationController, and having all the administrative controllers inherit from it. Or perhaps in a simpler manner, by defining the authenticate method within ApplicationController so that is accessible to both controllers.

It's also worth pointing out that with the namespaces provided by routing, it may be worth separating the publicly available actions from the administrative ones; for example, having index and show within ArticlesController and moving the other five actions to Admin::ArticlesController. The URLswould change, the helpers for the actions defined within the administrative articles controller would be prefixed by admin, and therefore it would be easy to distinguish between the two controllers. Keep in mind, though, that this would require quite a bit of renaming in both the controller layer and in the view layer. For a larger project, this method of separation would be recommended.

Feel free to experiment with these and other ideas that you might have, because making mistakes and figuring out how to solve them is an important part of the process of learning.

Finally, for real applications, consider using the restful_authentication plugin instead. This is the plugin used by most Rails sites out there and it has many useful features, including secure password handling and the ability to send validation emails before activating accounts. You could write your own user authentication and authorization system, but the restful_authentication plugin is usually a good starting point. Checking its code is also an excellent way to learn more about "best practices"; for example, how to encrypt passwords in the database (with a cryptographic salt for added security). You can find its code online on GitHub:

http://github.com/technoweenie/restful-authentication/tree/master

There is also a fork that's aimed at being internationalization-friendly, called restful-authentication-i18n. This too is available on GitHub, which is now the most popular repository site for Ruby and Rails code, outside of RubyForge.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.85.135