routing-filter 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,82 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ routing-filter (0.2.0)
5
+ actionpack
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ abstract (1.0.0)
11
+ actionmailer (3.0.3)
12
+ actionpack (= 3.0.3)
13
+ mail (~> 2.2.9)
14
+ actionpack (3.0.3)
15
+ activemodel (= 3.0.3)
16
+ activesupport (= 3.0.3)
17
+ builder (~> 2.1.2)
18
+ erubis (~> 2.6.6)
19
+ i18n (~> 0.4)
20
+ rack (~> 1.2.1)
21
+ rack-mount (~> 0.6.13)
22
+ rack-test (~> 0.5.6)
23
+ tzinfo (~> 0.3.23)
24
+ activemodel (3.0.3)
25
+ activesupport (= 3.0.3)
26
+ builder (~> 2.1.2)
27
+ i18n (~> 0.4)
28
+ activerecord (3.0.3)
29
+ activemodel (= 3.0.3)
30
+ activesupport (= 3.0.3)
31
+ arel (~> 2.0.2)
32
+ tzinfo (~> 0.3.23)
33
+ activeresource (3.0.3)
34
+ activemodel (= 3.0.3)
35
+ activesupport (= 3.0.3)
36
+ activesupport (3.0.3)
37
+ arel (2.0.6)
38
+ builder (2.1.2)
39
+ erubis (2.6.6)
40
+ abstract (>= 1.0.0)
41
+ i18n (0.5.0)
42
+ mail (2.2.12)
43
+ activesupport (>= 2.3.6)
44
+ i18n (>= 0.4.0)
45
+ mime-types (~> 1.16)
46
+ treetop (~> 1.4.8)
47
+ mime-types (1.16)
48
+ polyglot (0.3.1)
49
+ rack (1.2.1)
50
+ rack-mount (0.6.13)
51
+ rack (>= 1.0.0)
52
+ rack-test (0.5.6)
53
+ rack (>= 1.0)
54
+ rails (3.0.3)
55
+ actionmailer (= 3.0.3)
56
+ actionpack (= 3.0.3)
57
+ activerecord (= 3.0.3)
58
+ activeresource (= 3.0.3)
59
+ activesupport (= 3.0.3)
60
+ bundler (~> 1.0)
61
+ railties (= 3.0.3)
62
+ railties (3.0.3)
63
+ actionpack (= 3.0.3)
64
+ activesupport (= 3.0.3)
65
+ rake (>= 0.8.7)
66
+ thor (~> 0.14.4)
67
+ rake (0.8.7)
68
+ test_declarative (0.0.4)
69
+ thor (0.14.6)
70
+ treetop (1.4.9)
71
+ polyglot (>= 0.3.1)
72
+ tzinfo (0.3.23)
73
+
74
+ PLATFORMS
75
+ ruby
76
+
77
+ DEPENDENCIES
78
+ actionpack
79
+ i18n
80
+ rails
81
+ routing-filter!
82
+ test_declarative
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sven Fuchs
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,190 @@
1
+ # Routing Filter
2
+
3
+ Routing filters wrap around the complex beast that the Rails routing system is
4
+ to allow for unseen flexibility and power in Rails URL recognition and
5
+ generation.
6
+
7
+ As powerful and awesome the Rails' routes are, when you need to design your
8
+ URLs in a manner that only slightly leaves the paved cowpaths of Rails
9
+ conventions, you're usually unable to use all the goodness of helpers and
10
+ convenience that Rails ships with.
11
+
12
+ This library comes with four more or less reusable filters and it is easy to
13
+ implement custom ones. Maybe the most popular one is the Locale routing filter:
14
+
15
+ * `Locale` - prepends the page's :locale (e.g. /de/products)
16
+ * `Pagination` - appends page/:num (e.g. /products/page/2)
17
+ * `Uuid` - prepends a uuid for authentication or other purposes (e.g. /d00fbbd1-82b6-4c1a-a57d-098d529d6854/products/1)
18
+ * `Extension` - appends an extension (e.g. /products.html)
19
+
20
+ Please note that Rails 3's routing system is much more powerful and flexible
21
+ than Rails 2 was. There are many usecases that now can be covered with just
22
+ Rails 3 default routing features that weren't doable in Rails 2. For an example
23
+ of a quite complex and flexible route see this [gist by Andrew White](http://gist.github.com/653543)
24
+
25
+ ## Requirements
26
+
27
+ routing-filter currently only works with Rails. It should not be all too hard
28
+ to get it working with plain Rack::Mount but I haven't had that usecase, yet.
29
+
30
+ ## Installation
31
+
32
+ Just install the Gem:
33
+
34
+ $ gem install routing-filter
35
+
36
+ The Gem should work out of the box for Rails 3 after specifying it in your
37
+ application's Gemfile.
38
+
39
+ # Gemfile
40
+ gem 'routing-filter'
41
+
42
+ In order to use it with Rails 2.x you could specify it in your environment.rb
43
+
44
+ # config/environment.rb
45
+ gem 'routing-filter'
46
+
47
+ ## Usage
48
+
49
+ Once the Gem has loaded you can setup the filters in your routes file like this:
50
+
51
+ # in config/routes.rb
52
+ Rails.application.routes.draw do
53
+ filter :pagination, :uuid
54
+ end
55
+
56
+ Filters can also accept options:
57
+
58
+ Rails.application.routes.draw do
59
+ filter :extension, :exclude => %r(^admin/)
60
+ end
61
+
62
+ ## Filter order
63
+
64
+ 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.
65
+
66
+ 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.
67
+
68
+ 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).
69
+
70
+ 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.
71
+
72
+ 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.
73
+
74
+
75
+ ## Implementing your own filters
76
+
77
+ For example implementations have a look at the existing filters in
78
+ [lib/routing_filter/filters](http://github.com/svenfuchs/routing-filter/tree/master/lib/routing_filter/filters)
79
+
80
+ The following would be a sceleton of an empty filter:
81
+
82
+ module RoutingFilter
83
+ class Awesomeness < Filter
84
+ def around_recognize(path, env, &block)
85
+ # Alter the path here before it gets recognized.
86
+ # Make sure to yield (calls the next around filter if present and
87
+ # eventually `recognize_path` on the routeset):
88
+ yield.tap do |params|
89
+ # You can additionally modify the params here before they get passed
90
+ # to the controller.
91
+ end
92
+ end
93
+
94
+ def around_generate(params, &block)
95
+ # Alter arguments here before they are passed to `url_for`.
96
+ # Make sure to yield (calls the next around filter if present and
97
+ # eventually `url_for` on the controller):
98
+ yield.tap do |result|
99
+ # You can change the generated url_or_path here. Make sure to use
100
+ # one of the "in-place" modifying String methods though (like sub!
101
+ # and friends).
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ You can specify the filter explicitely in your routes.rb:
108
+
109
+ Rails.application.routes.draw do
110
+ filter :awesomeness
111
+ end
112
+
113
+ (I am not sure if it makes sense to provide more technical information than
114
+ this because the usage of this plugin definitely requires some advanced
115
+ knowledge about Rails internals and especially its routing system. So, I
116
+ figure, anyone who could use this should also be able to read the code and
117
+ figure out what it's doing much better then from any lengthy documentation.
118
+
119
+ If I'm mistaken on this please drop me an email with your suggestions.)
120
+
121
+
122
+ ## Rationale: Two example usecases
123
+
124
+ ### Conditionally prepending the locale
125
+
126
+ An early usecase from which this originated was the need to define a locale
127
+ at the beginning of an URL in a way so that
128
+
129
+ * the locale can be omitted when it is the default locale
130
+ * all the url\_helpers that are generated by named routes as well as url_for continue to work in
131
+ a concise manner (i.e. without specifying all parameters again and again)
132
+ * ideally also plays nicely with default route helpers in tests/specs
133
+
134
+ You can read about this struggle and two possible, yet unsatisfying solutions
135
+ [here](http://www.artweb-design.de/2007/5/13/concise-localized-rails-url-helpers-solved-twice).
136
+ The conclusion so far is that Rails itself does not provide the tools to solve
137
+ this problem in a clean and dry way.
138
+
139
+ ### Expanding /sections/:id to nested tree segments
140
+
141
+ Another usecase that eventually spawned the implementation of this plugin was
142
+ the need to map an arbitrary count of path segments to a certain model
143
+ instance. In an application that I've been working on recently I needed to
144
+ map URL paths to a nested tree of models like so:
145
+
146
+ root
147
+ + docs
148
+ + api
149
+ + wiki
150
+
151
+ E.g. the docs section should map to the path `/docs`, the api section to
152
+ the path `/docs/api` and so on. Furthermore, after these paths there need to be
153
+ more things to be specified. E.g. the wiki needs to define a whole Rails
154
+ resource with URLs like `/docs/wiki/pages/1/edit`.
155
+
156
+ The only way to solve this problem with Rails' routing toolkit is to map
157
+ a big, bold `/*everything` catch-all ("globbing") route and process the whole
158
+ path in a custom dispatcher.
159
+
160
+ This, of course, is a really unsatisfying solution because one has to
161
+ reimplement everything that Rails routes are here to help with: regarding both
162
+ URL recognition (like parameter mappings, resources, ...) and generation
163
+ (url\_helpers).
164
+
165
+ ## Solution
166
+
167
+ This plugin offers a solution that takes exactly the opposite route.
168
+
169
+ Instead of trying to change things *between* the URL recognition and
170
+ generation stages to achieve the desired result it *wraps around* the whole
171
+ routing system and allows to pre- and post-filter both what goes into it
172
+ (URL recognition) and what comes out of it (URL generation).
173
+
174
+ This way we can leave *everything* else completely untouched.
175
+
176
+ * We can tinker with the URLs that we receive from the server and feed URLs to
177
+ Rails that perfectly match the best breed of Rails' conventions.
178
+ * Inside of the application we can use all the nice helper goodness and
179
+ conveniences that rely on these conventions being followed.
180
+ * Finally we can accept URLs that have been generated by the url\_helpers and,
181
+ again, mutate them in the way that matches our requirements.
182
+
183
+ So, even though the plugin itself is a blatant monkey-patch to one of the
184
+ most complex area of Rails internals, this solution seems to be effectively
185
+ less intrusive and pricey than others are.
186
+
187
+ ## Etc
188
+
189
+ Authors: [Sven Fuchs](http://www.artweb-design.de) <svenfuchs at artweb-design dot de>
190
+ License: MIT
@@ -0,0 +1 @@
1
+ require 'routing_filter'
@@ -0,0 +1,27 @@
1
+ require 'action_pack'
2
+ require 'active_support/core_ext/string/inflections'
3
+
4
+ module RoutingFilter
5
+ autoload :Filter, 'routing_filter/filter'
6
+ autoload :Chain, 'routing_filter/chain'
7
+ autoload :Extension, 'routing_filter/filters/extension'
8
+ autoload :Locale, 'routing_filter/filters/locale'
9
+ autoload :Pagination, 'routing_filter/filters/pagination'
10
+ autoload :Uuid, 'routing_filter/filters/uuid'
11
+
12
+ class << self
13
+ def build(name, options)
14
+ const_get(name.to_s.camelize).new(options)
15
+ end
16
+
17
+ def active=(active)
18
+ @@active = active
19
+ end
20
+
21
+ def active?
22
+ defined?(@@active) ? @@active : @@active = true
23
+ end
24
+ end
25
+ end
26
+
27
+ require "routing_filter/adapters/rails_#{ActionPack::VERSION::MAJOR}"
@@ -0,0 +1,69 @@
1
+ require 'action_controller'
2
+
3
+ # allows to install a filter to the route set by calling: map.filter 'locale'
4
+ ActionController::Routing::RouteSet::Mapper.class_eval do
5
+ def filter(*args)
6
+ @set.add_filters(*args)
7
+ end
8
+ end
9
+
10
+ # same here for the optimized url generation in named routes
11
+ ActionController::Routing::RouteSet::NamedRouteCollection.class_eval do
12
+ # gosh. monkey engineering optimization code
13
+ def generate_optimisation_block_with_filtering(*args)
14
+ code = generate_optimisation_block_without_filtering(*args)
15
+ if match = code.match(%r(^return (.*) if (.*)))
16
+ # returned string must not contain newlines, or we'll spill out of inline code comments in
17
+ # ActionController::Routing::RouteSet::NamedRouteCollection#define_url_helper
18
+ "returning(#{match[1]}) { |result|" +
19
+ " ActionController::Routing::Routes.filters.run(:around_generate, *args, &lambda{ result }) " +
20
+ "} if #{match[2]}"
21
+ end
22
+ end
23
+ alias_method_chain :generate_optimisation_block, :filtering
24
+ end
25
+
26
+ ActionController::Routing::RouteSet.class_eval do
27
+ attr_writer :filters
28
+
29
+ def filters
30
+ @filters ||= RoutingFilter::Chain.new
31
+ end
32
+
33
+ def add_filters(*names)
34
+ options = names.extract_options!
35
+ names.each { |name| filters.unshift(RoutingFilter.build(name, options)) }
36
+ end
37
+
38
+ def recognize_path_with_filtering(path, env = {})
39
+ path = ::URI.unescape(path.dup) # string is frozen due to memoize
40
+ filters.run(:around_recognize, path, env, &lambda{ recognize_path_without_filtering(path, env) })
41
+ end
42
+ alias_method_chain :recognize_path, :filtering
43
+
44
+ def generate_with_filtering(*args)
45
+ filters.run(:around_generate, args.first, &lambda{ generate_without_filtering(*args) })
46
+ end
47
+ alias_method_chain :generate, :filtering
48
+
49
+ def clear_with_filtering!
50
+ @filters.clear if @filters
51
+ clear_without_filtering!
52
+ end
53
+ alias_method_chain :clear!, :filtering
54
+
55
+ # add some useful information to the request environment
56
+ # right, this is from jamis buck's excellent article about routes internals
57
+ # http://weblog.jamisbuck.org/2006/10/26/monkey-patching-rails-extending-routes-2
58
+ # TODO move this ... where?
59
+ alias_method :extract_request_environment_without_host, :extract_request_environment unless method_defined? :extract_request_environment_without_host
60
+ def extract_request_environment(request)
61
+ returning extract_request_environment_without_host(request) do |env|
62
+ env.merge! :host => request.host,
63
+ :port => request.port,
64
+ :host_with_port => request.host_with_port,
65
+ :domain => request.domain,
66
+ :subdomain => request.subdomains.first
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,71 @@
1
+ require 'action_dispatch'
2
+ require 'active_support/core_ext/module/aliasing'
3
+ require 'active_support/core_ext/hash/reverse_merge'
4
+
5
+ [ActionDispatch::Routing::Mapper, ActionDispatch::Routing::DeprecatedMapper].each do |mapper|
6
+ mapper.class_eval do
7
+ def filter(*args)
8
+ @set.add_filters(*args)
9
+ end
10
+ end
11
+ end
12
+
13
+ ActionDispatch::Routing::RouteSet.class_eval do
14
+ def add_filters(*names)
15
+ options = names.extract_options!
16
+ names.each { |name| @set.filters.unshift(RoutingFilter.build(name, options)) }
17
+ end
18
+
19
+ # def recognize_path_with_filtering(path, env = {})
20
+ # @set.filters.run(:around_recognize, path.dup, env, &lambda{ recognize_path_without_filtering(path.dup, env) })
21
+ # end
22
+ # alias_method_chain :recognize_path, :filtering
23
+
24
+ def generate_with_filtering(options, recall = {}, extras = false)
25
+ @set.filters.run(:around_generate, options, &lambda{ generate_without_filtering(options, recall, extras) })
26
+ end
27
+ alias_method_chain :generate, :filtering
28
+
29
+ def clear_with_filtering!
30
+ @set.filters.clear if @set
31
+ clear_without_filtering!
32
+ end
33
+ alias_method_chain :clear!, :filtering
34
+ end
35
+
36
+ require 'rack/mount/route_set'
37
+ require 'rack/mount/code_generation'
38
+
39
+ Rack::Mount::RouteSet.class_eval do
40
+ def filters
41
+ @filters || RoutingFilter::Chain.new.tap { |f| @filters = f unless frozen? }
42
+ end
43
+ end
44
+
45
+ # gah. so who's hoped monkeypatching optimized code wouldn't be necessary with rails 3 anymore?
46
+ Rack::Mount::CodeGeneration.class_eval do
47
+ def optimize_recognize_with_filtering!
48
+ optimize_recognize_without_filtering!
49
+ (class << self; self; end).class_eval do
50
+ alias_method_chain :recognize, :filtering
51
+ end
52
+ end
53
+ alias :optimize_recognize_without_filtering! :optimize_recognize!
54
+ alias :optimize_recognize! :optimize_recognize_with_filtering!
55
+
56
+ # note: if you overly and unnecessarily use blocks in your lowlevel libraries you make it fricking
57
+ # hard for your users to hook in anywhere
58
+ def recognize_with_filtering(request, &block)
59
+ path, route, matches, params = request.env['PATH_INFO'], nil, nil, nil
60
+ original_path = path.dup
61
+
62
+ filters.run(:around_recognize, path, request.env) do
63
+ route, matches, params = recognize_without_filtering(request)
64
+ params || {}
65
+ end
66
+
67
+ request.env['PATH_INFO'] = original_path # hmm ...
68
+ block.call(route, matches, params) if route
69
+ end
70
+ end
71
+