Module: ActionController::Filters::ClassMethods

Defined in:
lib/action_controller/filters.rb

Overview

Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do authentication, caching, or auditing before the intended action is performed. Or to do localization or output compression after the action has been performed.

Filters have access to the request, response, and all the instance variables set by other filters in the chain or by the action (in the case of after filters). Additionally, it's possible for a pre-processing before_filter to halt the processing before the intended action is processed by returning false or performing a redirect or render. This is especially useful for filters like authentication where you're not interested in allowing the action to be performed if the proper credentials are not in order.

Filter inheritance

Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without affecting the superclass. For example:

class BankController < ActionController::Base
  before_filter :audit

  private
    def audit
      # record the action and parameters in an audit log
    end
end

class VaultController < BankController
  before_filter :verify_credentials

  private
    def verify_credentials
      # make sure the user is allowed into the vault
    end
end

Now any actions performed on the BankController will have the audit method called before. On the VaultController, first the audit method is called, then the verify_credentials method. If the audit method returns false, then verify_credentials and the intended action is never called.

Filter types

A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.

Using an external class makes for more easily reused generic filters, such as output compression. External filter classes are implemented by having a static filter method on any class and then passing this class to the filter method. Example:

class OutputCompressionFilter
  def self.filter(controller)
    controller.response.body = compress(controller.response.body)
  end
end

class NewspaperController < ActionController::Base
  after_filter OutputCompressionFilter
end

The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can manipulate them as it sees fit.

The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation. Or just as a quick test. It works like this:

class WeblogController < ActionController::Base
  before_filter { |controller| false if controller.params["stop_action"] }
end

As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables. This means that the block has access to both the request and response objects complete with convenience methods for params, session, template, and assigns. Note: The inline method doesn't strictly has to be a block. Any object that responds to call and returns 1 or -1 on arity will do (such as a Proc or an Method object).

Filter chain ordering

Using before_filter and after_filter appends the specified filters to the existing chain. That's usually just fine, but some times you care more about the order in which the filters are executed. When that's the case, you can use prepend_before_filter and prepend_after_filter. Filters added by these methods will be put at the beginning of their respective chain and executed before the rest. For example:

class ShoppingController
  before_filter :verify_open_shop

class CheckoutController
  prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock

The filter chain for the CheckoutController is now :ensure_items_in_cart, :ensure_items_in_stock, :verify_open_shop. So if either of the ensure filters return false, we'll never get around to see if the shop is open or not.

You may pass multiple filter arguments of each type as well as a filter block. If a block is given, it is treated as the last argument.

Around filters

In addition to the individual before and after filters, it's also possible to specify that a single object should handle both the before and after call. That's especially useful when you need to keep state active between the before and after, such as the example of a benchmark filter below:

class WeblogController < ActionController::Base
  around_filter BenchmarkingFilter.new

  # Before this action is performed, BenchmarkingFilter#before(controller) is executed
  def index
  end
  # After this action has been performed, BenchmarkingFilter#after(controller) is executed
end

class BenchmarkingFilter
  def initialize
    @runtime
  end

  def before
    start_timer
  end

  def after
    stop_timer
    report_result
  end
end

Filter conditions

Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to exclude or the actions to include when executing the filter. Available conditions are :only or :except, both of which accept an arbitrary number of method references. For example:

class Journal < ActionController::Base
  # only require authentication if the current action is edit or delete
  before_filter :authorize, :only => [ :edit, :delete ]

  private
    def authorize
      # redirect to login unless authenticated
    end
end

When setting conditions on inline method (proc) filters the condition must come first and be placed in parenthesis.

class UserPreferences < ActionController::Base
  before_filter(:except => :new) { # some proc ... }
  # ...
end

Instance Method Summary collapse

Instance Method Details

#after_filtersObject

Returns all the after filters for this class and all its ancestors.


238
239
240
# File 'lib/action_controller/filters.rb', line 238

def after_filters #:nodoc:
  read_inheritable_attribute("after_filters")
end

#append_after_filter(*filters, &block) ⇒ Object Also known as: after_filter

The passed filters will be appended to the array of filters that's run after actions on this controller are performed.


177
178
179
180
181
182
# File 'lib/action_controller/filters.rb', line 177

def append_after_filter(*filters, &block)
  conditions = extract_conditions!(filters) 
  filters << block if block_given?
  add_action_conditions(filters, conditions)
  append_filter_to_chain('after', filters)
end

#append_around_filter(*filters) ⇒ Object Also known as: around_filter

The passed filters will have their before method appended to the array of filters that's run both before actions on this controller are performed and have their after method prepended to the after actions. The filter objects must all respond to both before and after. So if you do append_around_filter A.new, B.new, the callstack will look like:

B#before
  A#before
  A#after
B#after

204
205
206
207
208
209
210
211
# File 'lib/action_controller/filters.rb', line 204

def append_around_filter(*filters)
  conditions = extract_conditions!(filters) 
  for filter in filters.flatten
    ensure_filter_responds_to_before_and_after(filter)
    append_before_filter(conditions || {}) { |c| filter.before(c) }
    prepend_after_filter(conditions || {}) { |c| filter.after(c) }
  end
end

#append_before_filter(*filters, &block) ⇒ Object Also known as: before_filter

The passed filters will be appended to the array of filters that's run before actions on this controller are performed.


156
157
158
159
160
161
# File 'lib/action_controller/filters.rb', line 156

def append_before_filter(*filters, &block)
  conditions = extract_conditions!(filters)
  filters << block if block_given?
  add_action_conditions(filters, conditions)
  append_filter_to_chain('before', filters)
end

#before_filtersObject

Returns all the before filters for this class and all its ancestors.


233
234
235
# File 'lib/action_controller/filters.rb', line 233

def before_filters #:nodoc:
  read_inheritable_attribute("before_filters")
end

#excluded_actionsObject

Returns a mapping between filters and actions that may not run them.


248
249
250
# File 'lib/action_controller/filters.rb', line 248

def excluded_actions #:nodoc:
  read_inheritable_attribute("excluded_actions") || {}
end

#included_actionsObject

Returns a mapping between filters and the actions that may run them.


243
244
245
# File 'lib/action_controller/filters.rb', line 243

def included_actions #:nodoc:
  read_inheritable_attribute("included_actions") || {}
end

#prepend_after_filter(*filters, &block) ⇒ Object

The passed filters will be prepended to the array of filters that's run after actions on this controller are performed.


186
187
188
189
190
191
# File 'lib/action_controller/filters.rb', line 186

def prepend_after_filter(*filters, &block)
  conditions = extract_conditions!(filters) 
  filters << block if block_given?
  add_action_conditions(filters, conditions)
  prepend_filter_to_chain("after", filters)
end

#prepend_around_filter(*filters) ⇒ Object

The passed filters will have their before method prepended to the array of filters that's run both before actions on this controller are performed and have their after method appended to the after actions. The filter objects must all respond to both before and after. So if you do prepend_around_filter A.new, B.new, the callstack will look like:

A#before
  B#before
  B#after
A#after

221
222
223
224
225
226
227
# File 'lib/action_controller/filters.rb', line 221

def prepend_around_filter(*filters)
  for filter in filters.flatten
    ensure_filter_responds_to_before_and_after(filter)
    prepend_before_filter { |c| filter.before(c) }
    append_after_filter   { |c| filter.after(c) }
  end
end

#prepend_before_filter(*filters, &block) ⇒ Object

The passed filters will be prepended to the array of filters that's run before actions on this controller are performed.


165
166
167
168
169
170
# File 'lib/action_controller/filters.rb', line 165

def prepend_before_filter(*filters, &block)
  conditions = extract_conditions!(filters) 
  filters << block if block_given?
  add_action_conditions(filters, conditions)
  prepend_filter_to_chain('before', filters)
end