Module: Reactive::Mvc::Controller::Filters::ClassMethods

Defined in:
lib/reactive-mvc/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).

Filter inheritance

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

class BankController < Reactive::Controller::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 renders or redirects, then verify_credentials and the intended action are 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 < Reactive::Controller::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 < Reactive::Controller::Base
  before_filter { |controller| head(400) 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 have 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).

Please note that around_filters function a little differently than the normal before and after filters with regard to filter types. Please see the section dedicated to around_filters below.

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 < Reactive::Controller::Base
  before_filter :verify_open_shop

class CheckoutController < ShoppingController
  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 renders or redirects, 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

Around filters wrap an action, executing code both before and after. They may be declared as method references, blocks, or objects responding to #filter or to both #before and #after.

To use a method as an around_filter, pass a symbol naming the Ruby method. Yield (or block.call) within the method to run the action.

around_filter :catch_exceptions

private
  def catch_exceptions
    yield
  rescue => exception
    logger.debug "Caught exception! #{exception}"
    raise
  end

To use a block as an around_filter, pass a block taking as args both the controller and the action block. You can’t call yield directly from an around_filter block; explicitly call the action block instead:

around_filter do |controller, action|
  logger.debug "before #{controller.action_name}"
  action.call
  logger.debug "after #{controller.action_name}"
end

To use a filter object with around_filter, pass an object responding to :filter or both :before and :after. With a filter method, yield to the block as above:

around_filter BenchmarkingFilter

class BenchmarkingFilter
  def self.filter(controller, &block)
    Benchmark.measure(&block)
  end
end

With before and after methods:

around_filter Authorizer.new

class Authorizer
  # This will run before the action. Redirecting aborts the action.
  def before(controller)
    unless user.authorized?
      redirect_to()
    end
  end

  # This will run after the action if and only if before did not render or redirect.
  def after(controller)
  end
end

If the filter has before and after methods, the before method will be called before the action. If before renders or redirects, the filter chain is halted and after will not be run. See Filter Chain Halting below for an example.

Filter chain skipping

Declaring a filter on a base class conveniently applies to its subclasses, but sometimes a subclass should skip some of its superclass’ filters:

class ApplicationController < Reactive::Controller::Base
  before_filter :authenticate
  around_filter :catch_exceptions
end

class WeblogController < ApplicationController
  # Will run the :authenticate and :catch_exceptions filters.
end

class SignupController < ApplicationController
  # Skip :authenticate, run :catch_exceptions.
  skip_before_filter :authenticate
end

class ProjectsController < ApplicationController
  # Skip :catch_exceptions, run :authenticate.
  skip_filter :catch_exceptions
end

class ClientsController < ApplicationController
  # Skip :catch_exceptions and :authenticate unless action is index.
  skip_filter :catch_exceptions, :authenticate, :except => :index
end

Filter conditions

Filters may be limited to specific actions by declaring the actions to include or exclude. Both options accept single actions (:only => :index) or arrays of actions (:except => [:foo, :bar]).

class Journal < Reactive::Controller::Base
  # Require authentication for edit and delete.
  before_filter :authorize, :only => [:edit, :delete]

  # Passing options to a filter with a block.
  around_filter(:except => :index) do |controller, action_block|
    results = Profiler.run(&action_block)
    controller.response.sub! "</body>", "#{results}</body>"
  end

  private
    def authorize
      # Redirect to login unless authenticated.
    end
end

Filter Chain Halting

before_filter and around_filter may halt the request before a controller action is run. This is useful, for example, to deny access to unauthenticated users or to redirect from http to https. Simply call render or redirect. After filters will not be executed if the filter chain is halted.

Around filters halt the request unless the action block is called. Given these filters

after_filter :after
around_filter :around
before_filter :before

The filter chain will look like:

...
. \
.  #around (code before yield)
.  .  \
.  .  #before (actual filter code is run)
.  .  .  \
.  .  .  execute controller action
.  .  .  /
.  .  ...
.  .  /
.  #around (code after yield)
. /
#after (actual filter code is run, unless the around filter does not yield)

If #around returns before yielding, #after will still not be run. The #before filter and controller action will not be run. If #before renders or redirects, the second half of #around and will still run but #after and the action will not. If #around fails to yield, #after will not be run.

Defined Under Namespace

Classes: AfterFilterProxy, BeforeFilterProxy, ClassAfterFilter, ClassBeforeFilter, ClassFilter, Filter, FilterProxy, MethodFilter, ProcFilter, ProcWithCallFilter, SymbolFilter

Instance Method Summary collapse

Instance Method Details

#after_filtersObject

Returns all the after filters for this class and all its ancestors. This method returns the actual filter that was assigned in the controller to maintain existing functionality.



359
360
361
# File 'lib/reactive-mvc/controller/filters.rb', line 359

def after_filters #:nodoc:
  filter_chain.select(&:after?).map(&:filter)
end

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

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



265
266
267
# File 'lib/reactive-mvc/controller/filters.rb', line 265

def append_after_filter(*filters, &block)
  append_filter_to_chain(filters, :after, &block)
end

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

If you append_around_filter A.new, B.new, the filter chain looks like

B#before
  A#before
    # run the action
  A#after
B#after

With around filters which yield to the action block, #before and #after are the code before and after the yield.



289
290
291
292
293
294
# File 'lib/reactive-mvc/controller/filters.rb', line 289

def append_around_filter(*filters, &block)
  filters, conditions = extract_conditions(filters, &block)
  filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
    append_filter_to_chain([filter, conditions])
  end
end

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

The passed filters will be appended to the filter_chain and will execute before the action on this controller is performed.



250
251
252
# File 'lib/reactive-mvc/controller/filters.rb', line 250

def append_before_filter(*filters, &block)
  append_filter_to_chain(filters, :before, &block)
end

#before_filtersObject

Returns all the before filters for this class and all its ancestors. This method returns the actual filter that was assigned in the controller to maintain existing functionality.



353
354
355
# File 'lib/reactive-mvc/controller/filters.rb', line 353

def before_filters #:nodoc:
  filter_chain.select(&:before?).map(&:filter)
end

#excluded_actionsObject

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



369
370
371
# File 'lib/reactive-mvc/controller/filters.rb', line 369

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

#filter_chainObject

Returns an array of Filter objects for this controller.



347
348
349
# File 'lib/reactive-mvc/controller/filters.rb', line 347

def filter_chain
  read_inheritable_attribute("filter_chain") || []
end

#filter_excluded_from_action?(filter, action) ⇒ Boolean

Returns true if the filter is excluded from the given action

Returns:

  • (Boolean)


383
384
385
386
387
388
389
390
# File 'lib/reactive-mvc/controller/filters.rb', line 383

def filter_excluded_from_action?(filter,action) #:nodoc:
  case
  when ia = included_actions[filter]
    !ia.include?(action)
  when ea = excluded_actions[filter]
    ea.include?(action)
  end
end

#find_filter(filter, &block) ⇒ Object

Find a filter in the filter_chain where the filter method matches the filter param and (optionally) the passed block evaluates to true (mostly used for testing before? and after? on the filter). Useful for symbol filters.

The object of type Filter is passed to the block when yielded, not the filter itself.



378
379
380
# File 'lib/reactive-mvc/controller/filters.rb', line 378

def find_filter(filter, &block) #:nodoc:
  filter_chain.select { |f| f.filter == filter && (!block_given? || yield(f)) }.first
end

#included_actionsObject

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



364
365
366
# File 'lib/reactive-mvc/controller/filters.rb', line 364

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

#prepend_after_filter(*filters, &block) ⇒ Object

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



271
272
273
# File 'lib/reactive-mvc/controller/filters.rb', line 271

def prepend_after_filter(*filters, &block)
  prepend_filter_to_chain(filters, :after, &block)
end

#prepend_around_filter(*filters, &block) ⇒ Object

If you prepend_around_filter A.new, B.new, the filter chain looks like:

A#before
  B#before
    # run the action
  B#after
A#after

With around filters which yield to the action block, #before and #after are the code before and after the yield.



306
307
308
309
310
311
# File 'lib/reactive-mvc/controller/filters.rb', line 306

def prepend_around_filter(*filters, &block)
  filters, conditions = extract_conditions(filters, &block)
  filters.map { |f| proxy_before_and_after_filter(f) }.each do |filter|
    prepend_filter_to_chain([filter, conditions])
  end
end

#prepend_before_filter(*filters, &block) ⇒ Object

The passed filters will be prepended to the filter_chain and will execute before the action on this controller is performed.



256
257
258
# File 'lib/reactive-mvc/controller/filters.rb', line 256

def prepend_before_filter(*filters, &block)
  prepend_filter_to_chain(filters, :before, &block)
end

#skip_after_filter(*filters) ⇒ Object

Removes the specified filters from the after filter chain. Note that this only works for skipping method-reference filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out of many sub-controllers need a different hierarchy.

You can control the actions to skip the filter for with the :only and :except options, just like when you apply the filters.



332
333
334
# File 'lib/reactive-mvc/controller/filters.rb', line 332

def skip_after_filter(*filters)
  skip_filter_in_chain(*filters, &:after?)
end

#skip_before_filter(*filters) ⇒ Object

Removes the specified filters from the before filter chain. Note that this only works for skipping method-reference filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out of many sub-controllers need a different hierarchy.

You can control the actions to skip the filter for with the :only and :except options, just like when you apply the filters.



322
323
324
# File 'lib/reactive-mvc/controller/filters.rb', line 322

def skip_before_filter(*filters)
  skip_filter_in_chain(*filters, &:before?)
end

#skip_filter(*filters) ⇒ Object

Removes the specified filters from the filter chain. This only works for method reference (symbol) filters, not procs. This method is different from skip_after_filter and skip_before_filter in that it will match any before, after or yielding around filter.

You can control the actions to skip the filter for with the :only and :except options, just like when you apply the filters.



342
343
344
# File 'lib/reactive-mvc/controller/filters.rb', line 342

def skip_filter(*filters)
  skip_filter_in_chain(*filters)
end