josh-rack-mount 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +28 -0
- data/lib/rack/mount.rb +18 -0
- data/lib/rack/mount/const.rb +39 -0
- data/lib/rack/mount/exceptions.rb +5 -0
- data/lib/rack/mount/generation.rb +9 -0
- data/lib/rack/mount/generation/optimizations.rb +83 -0
- data/lib/rack/mount/generation/route.rb +108 -0
- data/lib/rack/mount/generation/route_set.rb +60 -0
- data/lib/rack/mount/mappers/merb.rb +143 -0
- data/lib/rack/mount/mappers/rails_classic.rb +164 -0
- data/lib/rack/mount/mappers/rails_draft.rb +179 -0
- data/lib/rack/mount/mappers/simple.rb +37 -0
- data/lib/rack/mount/nested_set.rb +106 -0
- data/lib/rack/mount/path_prefix.rb +20 -0
- data/lib/rack/mount/recognition.rb +8 -0
- data/lib/rack/mount/recognition/route.rb +94 -0
- data/lib/rack/mount/recognition/route_set.rb +54 -0
- data/lib/rack/mount/regexp_with_named_groups.rb +38 -0
- data/lib/rack/mount/request.rb +30 -0
- data/lib/rack/mount/route.rb +63 -0
- data/lib/rack/mount/route_set.rb +34 -0
- data/lib/rack/mount/utils.rb +149 -0
- data/rails/init.rb +2 -0
- metadata +86 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Joshua Peek
|
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.rdoc
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
= Rack::Mount
|
2
|
+
|
3
|
+
A stackable dynamic tree based Rack router.
|
4
|
+
|
5
|
+
Rack::Mount supports Rack's Cascade style of trying several routes until it finds one that is not a 404. This allows multiple routes to be nested or stacked on top of each other. Since the application endpoint can trigger the router to continue matching, middleware can be used to add arbitrary conditions to any route. This allows you to route based on other request attributes, session information, or even data dynamically pulled pulled from a database.
|
6
|
+
|
7
|
+
=== Usage
|
8
|
+
|
9
|
+
Rack::Mount provides a plugin API to build custom DSLs on top of.
|
10
|
+
|
11
|
+
The API is extremely minimal and only 3 methods are exposed as the public API.
|
12
|
+
|
13
|
+
<tt>Rack::Mount::RouteSet#add_route</tt>:: builder method for adding routes to the set
|
14
|
+
<tt>Rack::Mount::RouteSet#call</tt>:: Rack compatible recognition and dispatching method
|
15
|
+
<tt>Rack::Mount::RouteSet#url_for</tt>:: generatess path from identifiers or significant keys
|
16
|
+
|
17
|
+
=== Example
|
18
|
+
|
19
|
+
require 'rack/mount'
|
20
|
+
Routes = Rack::Mount::RouteSet.new do |set|
|
21
|
+
# add_route takes a rack application and conditions to match with
|
22
|
+
# conditions may be strings or regexps
|
23
|
+
# See Rack::Mount::RouteSet#add_route for more options.
|
24
|
+
set.add_route FooApp, :method => 'get' :path => %{/foo}
|
25
|
+
end
|
26
|
+
|
27
|
+
# The route set itself is a simple rack app you mount
|
28
|
+
run Routes
|
data/lib/rack/mount.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
require 'rack/mount/exceptions'
|
3
|
+
|
4
|
+
module Rack #:nodoc:
|
5
|
+
module Mount #:nodoc:
|
6
|
+
autoload :Const, 'rack/mount/const'
|
7
|
+
autoload :Generation, 'rack/mount/generation'
|
8
|
+
autoload :NestedSet, 'rack/mount/nested_set'
|
9
|
+
autoload :NestedSetExt, 'rack/mount/nested_set_ext'
|
10
|
+
autoload :PathPrefix, 'rack/mount/path_prefix'
|
11
|
+
autoload :Recognition, 'rack/mount/recognition'
|
12
|
+
autoload :RegexpWithNamedGroups, 'rack/mount/regexp_with_named_groups'
|
13
|
+
autoload :Request, 'rack/mount/request'
|
14
|
+
autoload :Route, 'rack/mount/route'
|
15
|
+
autoload :RouteSet, 'rack/mount/route_set'
|
16
|
+
autoload :Utils, 'rack/mount/utils'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Rack
|
2
|
+
module Mount
|
3
|
+
module Const #:nodoc:
|
4
|
+
RACK_ROUTING_ARGS = 'rack.routing_args'.freeze
|
5
|
+
RACK_MOUNT_DEBUG = 'RACKMOUNT_DEBUG'.freeze
|
6
|
+
|
7
|
+
begin
|
8
|
+
eval('/(?<foo>.*)/').named_captures
|
9
|
+
SUPPORTS_NAMED_CAPTURES = true
|
10
|
+
REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze
|
11
|
+
rescue SyntaxError, NoMethodError
|
12
|
+
SUPPORTS_NAMED_CAPTURES = false
|
13
|
+
REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
EOS_KEY = '$'.freeze
|
17
|
+
|
18
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
19
|
+
DELETE = 'PUT'.freeze
|
20
|
+
EMPTY_STRING = ''.freeze
|
21
|
+
GET = 'GET'.freeze
|
22
|
+
HEAD = 'HEAD'.freeze
|
23
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
24
|
+
POST = 'POST'.freeze
|
25
|
+
PUT = 'PUT'.freeze
|
26
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
27
|
+
SLASH = '/'.freeze
|
28
|
+
TEXT_SLASH_HTML = 'text/html'.freeze
|
29
|
+
|
30
|
+
DEFAULT_CONTENT_TYPE_HEADERS = {CONTENT_TYPE => TEXT_SLASH_HTML}.freeze
|
31
|
+
HTTP_METHODS = [GET, HEAD, POST, PUT, DELETE].freeze
|
32
|
+
|
33
|
+
OK = 'OK'.freeze
|
34
|
+
NOT_FOUND = 'Not Found'.freeze
|
35
|
+
OK_RESPONSE = [200, DEFAULT_CONTENT_TYPE_HEADERS, [OK].freeze].freeze
|
36
|
+
NOT_FOUND_RESPONSE = [404, DEFAULT_CONTENT_TYPE_HEADERS, [NOT_FOUND].freeze].freeze
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Rack
|
2
|
+
module Mount
|
3
|
+
module Generation
|
4
|
+
module Optimizations #:nodoc:
|
5
|
+
def freeze
|
6
|
+
optimize_call! unless frozen?
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
if ENV[Const::RACK_MOUNT_DEBUG]
|
11
|
+
def instance_eval(*args)
|
12
|
+
puts
|
13
|
+
puts "#{args[1]}##{args[2]}"
|
14
|
+
puts args[0]
|
15
|
+
puts
|
16
|
+
|
17
|
+
super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def optimize_call!
|
23
|
+
@recognition_graph.lists.each do |list|
|
24
|
+
body = (0...list.length).zip(list).map { |i, route|
|
25
|
+
assign_index_params = assign_index_params(route)
|
26
|
+
<<-EOS
|
27
|
+
if #{route.method ? "method == #{route.method.inspect} && " : ''}path =~ #{route.path.inspect}
|
28
|
+
route = self[#{i}]
|
29
|
+
#{if assign_index_params.any?
|
30
|
+
'routing_args, param_matches = route.defaults.dup, $~.captures'
|
31
|
+
else
|
32
|
+
'routing_args = route.defaults.dup'
|
33
|
+
end}
|
34
|
+
#{assign_index_params.join("\n ")}
|
35
|
+
env[Const::RACK_ROUTING_ARGS] = routing_args
|
36
|
+
result = route.app.call(env)
|
37
|
+
return result unless result[0] == #{@catch}
|
38
|
+
end
|
39
|
+
EOS
|
40
|
+
}.join
|
41
|
+
|
42
|
+
method = <<-EOS, __FILE__, __LINE__
|
43
|
+
def optimized_each(env)
|
44
|
+
method = env[Const::REQUEST_METHOD]
|
45
|
+
path = env[Const::PATH_INFO]
|
46
|
+
#{body}
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
EOS
|
50
|
+
|
51
|
+
puts method if ENV[Const::RACK_MOUNT_DEBUG]
|
52
|
+
list.instance_eval(*method)
|
53
|
+
end
|
54
|
+
|
55
|
+
instance_eval(<<-EOS, __FILE__, __LINE__)
|
56
|
+
def call(env)
|
57
|
+
req = Request.new(env)
|
58
|
+
keys = [#{convert_keys_to_method_calls}]
|
59
|
+
@recognition_graph[*keys].optimized_each(env) || @throw
|
60
|
+
end
|
61
|
+
EOS
|
62
|
+
end
|
63
|
+
|
64
|
+
def convert_keys_to_method_calls
|
65
|
+
@recognition_keys.map { |key|
|
66
|
+
if key.is_a?(Array)
|
67
|
+
key = key.dup
|
68
|
+
"req.#{key.shift}(#{key.join(',')})"
|
69
|
+
else
|
70
|
+
"req.#{key}"
|
71
|
+
end
|
72
|
+
}.join(', ')
|
73
|
+
end
|
74
|
+
|
75
|
+
def assign_index_params(route)
|
76
|
+
route.instance_variable_get("@named_captures").map { |k, index|
|
77
|
+
"routing_args[#{k.inspect}] = param_matches[#{index}] if param_matches[#{index}]"
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Rack
|
2
|
+
module Mount
|
3
|
+
module Generation
|
4
|
+
module Route #:nodoc:
|
5
|
+
class DynamicSegment #:nodoc:
|
6
|
+
attr_reader :name, :requirement
|
7
|
+
|
8
|
+
def initialize(name, requirement)
|
9
|
+
@name, @requirement = name.to_sym, requirement
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(obj)
|
13
|
+
@name == obj.name && @requirement == obj.requirement
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(*args)
|
18
|
+
super
|
19
|
+
|
20
|
+
@segments = segments(@path).freeze
|
21
|
+
@required_params = @segments.find_all { |s|
|
22
|
+
s.is_a?(DynamicSegment)
|
23
|
+
}.map { |s| s.name }.freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
def url_for(params = {})
|
27
|
+
params = (params || {}).dup
|
28
|
+
|
29
|
+
return nil if @segments.empty?
|
30
|
+
return nil unless @required_params.all? { |p| params.include?(p) }
|
31
|
+
|
32
|
+
path = generate_from_segments(@segments, params, @defaults)
|
33
|
+
|
34
|
+
@defaults.each do |key, value|
|
35
|
+
params.delete(key)
|
36
|
+
end
|
37
|
+
|
38
|
+
if params.any?
|
39
|
+
path << "?#{Rack::Utils.build_query(params)}"
|
40
|
+
end
|
41
|
+
|
42
|
+
path
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
# Segment data structure used for generations
|
47
|
+
# => ['/people', ['.', :format]]
|
48
|
+
def segments(regexp)
|
49
|
+
parse_segments(Utils.extract_regexp_parts(regexp))
|
50
|
+
rescue ArgumentError
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse_segments(segments)
|
55
|
+
s = []
|
56
|
+
segments.each do |part|
|
57
|
+
if part.is_a?(Utils::Capture)
|
58
|
+
if part.named?
|
59
|
+
source = part.map { |p| p.is_a?(Array) ? "(#{p.join})?" : p }.join
|
60
|
+
requirement = Regexp.compile(source)
|
61
|
+
s << DynamicSegment.new(part.name, requirement)
|
62
|
+
else
|
63
|
+
s << parse_segments(part)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
source = part.gsub('\\.', '.').gsub('\\/', '/')
|
67
|
+
if Regexp.compile("^(#{part})$") =~ source
|
68
|
+
s << source
|
69
|
+
else
|
70
|
+
raise ArgumentError, "failed to parse #{part.inspect}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
s
|
75
|
+
end
|
76
|
+
|
77
|
+
def generate_from_segments(segments, params, defaults, optional = false)
|
78
|
+
if optional
|
79
|
+
return Const::EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
|
80
|
+
return Const::EMPTY_STRING if segments.flatten.all? { |s|
|
81
|
+
if s.is_a?(DynamicSegment) && params[s.name]
|
82
|
+
params[s.name].to_s !~ s.requirement
|
83
|
+
else
|
84
|
+
true
|
85
|
+
end
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
generated = segments.map do |segment|
|
90
|
+
case segment
|
91
|
+
when String
|
92
|
+
segment
|
93
|
+
when DynamicSegment
|
94
|
+
params[segment.name] || defaults[segment.name]
|
95
|
+
when Array
|
96
|
+
generate_from_segments(segment, params, defaults, true) || Const::EMPTY_STRING
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Delete any used items from the params
|
101
|
+
segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
|
102
|
+
|
103
|
+
generated.join
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Rack
|
2
|
+
module Mount
|
3
|
+
module Generation
|
4
|
+
module RouteSet
|
5
|
+
DEFAULT_KEYS = [] # [:controller, :action].freeze
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@named_routes = {}
|
9
|
+
@generation_keys = DEFAULT_KEYS
|
10
|
+
@generation_graph = NestedSet.new
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_route(*args)
|
15
|
+
route = super
|
16
|
+
|
17
|
+
@named_routes[route.name] = route if route.name
|
18
|
+
|
19
|
+
keys = @generation_keys.map { |key| route.defaults[key] }
|
20
|
+
@generation_graph[*keys] = route
|
21
|
+
|
22
|
+
route
|
23
|
+
end
|
24
|
+
|
25
|
+
def url_for(*args)
|
26
|
+
params = args.last.is_a?(Hash) ? args.pop : {}
|
27
|
+
named_route = args.shift
|
28
|
+
route = nil
|
29
|
+
|
30
|
+
if named_route
|
31
|
+
unless route = @named_routes[named_route.to_sym]
|
32
|
+
raise RoutingError, "#{named_route} failed to generate from #{params.inspect}"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
keys = @generation_keys.map { |key| params[key] }
|
36
|
+
@generation_graph[*keys].each do |r|
|
37
|
+
if r.defaults.all? { |k, v| params[k] == v }
|
38
|
+
route = r
|
39
|
+
break
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
unless route
|
44
|
+
raise RoutingError, "No route matches #{params.inspect}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
route.url_for(params)
|
49
|
+
end
|
50
|
+
|
51
|
+
def freeze
|
52
|
+
@named_routes.freeze
|
53
|
+
@generation_keys.freeze
|
54
|
+
@generation_graph.freeze
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
require 'merb-core/dispatch/router'
|
3
|
+
require 'rack/request'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Mount
|
7
|
+
class RouteSet
|
8
|
+
def prepare(*args, &block)
|
9
|
+
Mappers::Merb.new(self).prepare(*args, &block)
|
10
|
+
freeze
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Mappers
|
15
|
+
class Merb
|
16
|
+
class ::Merb::Router::Behavior
|
17
|
+
def to_route
|
18
|
+
raise Error, 'The route has already been committed.' if @route
|
19
|
+
|
20
|
+
controller = @params[:controller]
|
21
|
+
|
22
|
+
if prefixes = @options[:controller_prefix]
|
23
|
+
controller ||= ':controller'
|
24
|
+
|
25
|
+
prefixes.reverse_each do |prefix|
|
26
|
+
break if controller =~ %r{^/(.*)} && controller = $1
|
27
|
+
controller = "#{prefix}/#{controller}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@params.merge!(:controller => controller.to_s.gsub(%r{^/}, '')) if controller
|
32
|
+
|
33
|
+
identifiers = @identifiers.sort { |(first,_),(sec,_)| first <=> sec || 1 }
|
34
|
+
|
35
|
+
Thread.current[:merb_routes] << [
|
36
|
+
@conditions.dup,
|
37
|
+
@params,
|
38
|
+
@blocks,
|
39
|
+
{ :defaults => @defaults.dup, :identifiers => identifiers }
|
40
|
+
]
|
41
|
+
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class DeferredProc
|
47
|
+
def initialize(app, deferred_procs)
|
48
|
+
@app, @proc = app, deferred_procs.cache
|
49
|
+
end
|
50
|
+
|
51
|
+
def call(env)
|
52
|
+
# TODO: Change this to a Merb request
|
53
|
+
request = Rack::Request.new(env)
|
54
|
+
params = env[Const::RACK_ROUTING_ARGS]
|
55
|
+
result = @proc.call(request, params)
|
56
|
+
|
57
|
+
if result
|
58
|
+
@app.call(env)
|
59
|
+
else
|
60
|
+
Const::NOT_FOUND_RESPONSE
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class RequestConditions
|
66
|
+
def initialize(app, conditions)
|
67
|
+
@app, @conditions = app, conditions
|
68
|
+
end
|
69
|
+
|
70
|
+
def call(env)
|
71
|
+
# TODO: Change this to a Merb request
|
72
|
+
request = Rack::Request.new(env)
|
73
|
+
|
74
|
+
@conditions.each do |method, expected|
|
75
|
+
unless request.send(method) == expected
|
76
|
+
return Const::NOT_FOUND_RESPONSE
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
@app.call(env)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
DynamicController = lambda { |env|
|
85
|
+
app = ActiveSupport::Inflector.camelize("#{env[Const::RACK_ROUTING_ARGS][:controller]}Controller")
|
86
|
+
app = ActiveSupport::Inflector.constantize(app)
|
87
|
+
app.call(env)
|
88
|
+
}
|
89
|
+
|
90
|
+
attr_accessor :root_behavior
|
91
|
+
|
92
|
+
def initialize(set)
|
93
|
+
@set = set
|
94
|
+
@root_behavior = ::Merb::Router::Behavior.new.defaults(:action => 'index')
|
95
|
+
end
|
96
|
+
|
97
|
+
def prepare(first = [], last = [], &block)
|
98
|
+
Thread.current[:merb_routes] = []
|
99
|
+
begin
|
100
|
+
root_behavior._with_proxy(&block)
|
101
|
+
routes = Thread.current[:merb_routes]
|
102
|
+
routes.each { |route| add_route(*route) }
|
103
|
+
self
|
104
|
+
ensure
|
105
|
+
Thread.current[:merb_routes] = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def add_route(conditions, params, deferred_procs, options = {})
|
110
|
+
new_conditions = {}
|
111
|
+
new_conditions[:path] = conditions.delete(:path)[0]
|
112
|
+
new_conditions[:method] = conditions.delete(:method)
|
113
|
+
|
114
|
+
requirements = {}
|
115
|
+
conditions.each do |k, v|
|
116
|
+
if v.is_a?(Regexp)
|
117
|
+
requirements[k.to_sym] = conditions.delete(k)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
if new_conditions[:path].is_a?(String)
|
122
|
+
new_conditions[:path] = Utils.convert_segment_string_to_regexp(
|
123
|
+
new_conditions[:path], requirements, %w( / . ? ))
|
124
|
+
end
|
125
|
+
|
126
|
+
app = params.has_key?(:controller) ?
|
127
|
+
ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.camelize("#{params[:controller]}Controller")) :
|
128
|
+
DynamicController
|
129
|
+
|
130
|
+
if deferred_procs.any?
|
131
|
+
app = DeferredProc.new(app, deferred_procs.first)
|
132
|
+
end
|
133
|
+
|
134
|
+
if conditions.any?
|
135
|
+
app = RequestConditions.new(app, conditions)
|
136
|
+
end
|
137
|
+
|
138
|
+
@set.add_route(app, new_conditions, params)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|