Permalink
Browse files

Revert default order of filters by unshifting them to the chain

This is basically the same as rails does with engines, unshifting the load order of config/routes.rb files to the routes reloader. This way we end up with the common/base filters being run first and custom/app-level filters being run second. 

The reasoning behind this is that common filters (such as locale, section paths filters etc.) should not need to know about what custom, higher level filters do and thus need to run first. Custom filters on the other hand can easily take into account that common filters already have been run.
  • Loading branch information...
1 parent aa10928 commit 1133ce9ddfa3397d56ce172c64b2e3a54c3c2f44 Sven Fuchs committed Oct 28, 2010
View
@@ -54,6 +54,19 @@ Filters can also accept options:
filter :extension, :exclude => %r(^admin/)
end
+## Filter order
+
+You can picture the way routing-filter wraps filters around your application as a russian puppet pattern. Your application sits in the center and is wrapped by a number of filters. An incoming request's path will be past through these layers of filters from the outside in until it is passed to the regular application routes set. When you generate URLs on the other hand then the filters will be run from the inside out.
+
+Filter order might be confusing at first. The reason for that is that the way rack/mount (which is used by Rails as a core routing engine) is confusing in this respect and Rails tries to make the best of it.
+
+Suppose you have a filter :custom in your application routes.rb file and an engine that adds a :common filter. Then Rails makes it so that your application's routes file will be loaded first (basically route.rb files are loaded in reverse engine load order).
+
+Thus routing-filter will make your :custom filter the *inner-most* filter, wrapping the application *first*. The :common filter from your engine will be wrapped *around* that onion and will be made the *outer-most* filter.
+
+This way common base filters (such as the locale filter) can run first and do not need to know about the specifics of other (more specialized, custom) filters. Custom filters on the other hand can easily take into account that common filters might already have run and adjust accordingly.
+
+
## Implementing your own filters
For example implementations have a look at the existing filters in
@@ -169,4 +182,4 @@ less intrusive and pricey than others are.
## Etc
Authors: [Sven Fuchs](http://www.artweb-design.de) <svenfuchs at artweb-design dot de>
-License: MIT
+License: MIT
@@ -32,7 +32,7 @@ def filters
def add_filters(*names)
options = names.extract_options!
- names.each { |name| filters << RoutingFilter.build(name, options) }
+ names.each { |name| filters.unshift(RoutingFilter.build(name, options)) }
end
def recognize_path_with_filtering(path, env = {})
@@ -66,4 +66,4 @@ def extract_request_environment(request)
:subdomain => request.subdomains.first
end
end
-end
+end
@@ -13,7 +13,7 @@ def filter(*args)
ActionDispatch::Routing::RouteSet.class_eval do
def add_filters(*names)
options = names.extract_options!
- names.each { |name| @set.filters << RoutingFilter.build(name, options) }
+ names.each { |name| @set.filters.unshift(RoutingFilter.build(name, options)) }
end
# def recognize_path_with_filtering(path, env = {})
@@ -4,13 +4,19 @@ def <<(filter)
filter.previous, last.next = last, filter if last
super
end
-
+ alias push <<
+
+ def unshift(filter)
+ filter.next, first.previous = first, filter if first
+ super
+ end
+
def run(method, *args, &final)
active? ? first.run(method, *args, &final) : final.call
end
-
+
def active?
RoutingFilter.active? && !empty?
end
end
-end
+end
@@ -5,42 +5,43 @@
class RoutingFilterTest < Test::Unit::TestCase
class FooFilter < Filter
attr_reader :name
-
+
def initialize(name)
@name = name
end
-
+
def foo(log, &block)
log << name
yield
end
end
-
+
attr_reader :chain
-
+
def setup
@chain = Chain.new
- @chain << FooFilter.new('first') << FooFilter.new('second')
+ @chain.unshift FooFilter.new('custom filter')
+ @chain.unshift FooFilter.new('common filter')
end
-
+
test "filter.previous is nil for the first filter in the chain" do
assert_nil chain.first.previous
end
-
+
test "filter.previous returns the previous filter in the chain" do
assert_equal chain.first, chain.last.previous
end
-
+
test "filter.next is nil for the last filter in the chain" do
assert_nil chain.last.next
end
-
+
test "filter.next returns the next filter in the chain" do
assert_equal chain.last, chain.first.next
end
-
- test "chain.run calls the given method on registered filters in the given order" do
+
+ test "chain.run calls the given method on registered filters in reverse order" do
log = []
- assert_equal %w(first second finalizer), chain.run(:foo, log, &lambda { log << 'finalizer' })
+ assert_equal 'common filter, custom filter, finalizer', chain.run(:foo, log, &lambda { log << 'finalizer' }).join(', ')
end
-end
+end

0 comments on commit 1133ce9

Please sign in to comment.