tennpipes-base 3.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.rdoc +294 -0
  4. data/Rakefile +1 -0
  5. data/bin/tennpipes +8 -0
  6. data/lib/tennpipes-base.rb +196 -0
  7. data/lib/tennpipes-base/application.rb +175 -0
  8. data/lib/tennpipes-base/application/application_setup.rb +202 -0
  9. data/lib/tennpipes-base/application/authenticity_token.rb +25 -0
  10. data/lib/tennpipes-base/application/flash.rb +229 -0
  11. data/lib/tennpipes-base/application/params_protection.rb +129 -0
  12. data/lib/tennpipes-base/application/routing.rb +1002 -0
  13. data/lib/tennpipes-base/application/show_exceptions.rb +50 -0
  14. data/lib/tennpipes-base/caller.rb +53 -0
  15. data/lib/tennpipes-base/cli/adapter.rb +33 -0
  16. data/lib/tennpipes-base/cli/base.rb +105 -0
  17. data/lib/tennpipes-base/cli/console.rb +20 -0
  18. data/lib/tennpipes-base/cli/launcher.rb +103 -0
  19. data/lib/tennpipes-base/cli/rake.rb +50 -0
  20. data/lib/tennpipes-base/cli/rake_tasks.rb +72 -0
  21. data/lib/tennpipes-base/command.rb +38 -0
  22. data/lib/tennpipes-base/ext/sinatra.rb +29 -0
  23. data/lib/tennpipes-base/filter.rb +52 -0
  24. data/lib/tennpipes-base/images/404.png +0 -0
  25. data/lib/tennpipes-base/images/500.png +0 -0
  26. data/lib/tennpipes-base/loader.rb +202 -0
  27. data/lib/tennpipes-base/logger.rb +492 -0
  28. data/lib/tennpipes-base/module.rb +58 -0
  29. data/lib/tennpipes-base/mounter.rb +308 -0
  30. data/lib/tennpipes-base/path_router.rb +119 -0
  31. data/lib/tennpipes-base/path_router/compiler.rb +110 -0
  32. data/lib/tennpipes-base/path_router/error_handler.rb +8 -0
  33. data/lib/tennpipes-base/path_router/matcher.rb +123 -0
  34. data/lib/tennpipes-base/path_router/route.rb +169 -0
  35. data/lib/tennpipes-base/reloader.rb +309 -0
  36. data/lib/tennpipes-base/reloader/rack.rb +26 -0
  37. data/lib/tennpipes-base/reloader/storage.rb +55 -0
  38. data/lib/tennpipes-base/router.rb +98 -0
  39. data/lib/tennpipes-base/server.rb +119 -0
  40. data/lib/tennpipes-base/tasks.rb +21 -0
  41. data/lib/tennpipes-base/version.rb +20 -0
  42. data/lib/tennpipes-base/version.rb~ +20 -0
  43. data/test/fixtures/app_gem/Gemfile +4 -0
  44. data/test/fixtures/app_gem/app/app.rb +3 -0
  45. data/test/fixtures/app_gem/app_gem.gemspec +17 -0
  46. data/test/fixtures/app_gem/lib/app_gem.rb +7 -0
  47. data/test/fixtures/app_gem/lib/app_gem/version.rb +3 -0
  48. data/test/fixtures/apps/complex.rb +32 -0
  49. data/test/fixtures/apps/demo_app.rb +7 -0
  50. data/test/fixtures/apps/demo_demo.rb +7 -0
  51. data/test/fixtures/apps/demo_project/api/app.rb +7 -0
  52. data/test/fixtures/apps/demo_project/api/lib/api_lib.rb +3 -0
  53. data/test/fixtures/apps/demo_project/app.rb +7 -0
  54. data/test/fixtures/apps/external_apps/fake_lib.rb +1 -0
  55. data/test/fixtures/apps/external_apps/fake_root.rb +2 -0
  56. data/test/fixtures/apps/helpers/class_methods_helpers.rb +4 -0
  57. data/test/fixtures/apps/helpers/instance_methods_helpers.rb +4 -0
  58. data/test/fixtures/apps/helpers/support.rb +1 -0
  59. data/test/fixtures/apps/helpers/system_helpers.rb +8 -0
  60. data/test/fixtures/apps/kiq.rb +3 -0
  61. data/test/fixtures/apps/lib/myklass.rb +2 -0
  62. data/test/fixtures/apps/lib/myklass/mysubklass.rb +4 -0
  63. data/test/fixtures/apps/models/child.rb +2 -0
  64. data/test/fixtures/apps/models/parent.rb +5 -0
  65. data/test/fixtures/apps/mountable_apps/rack_apps.rb +15 -0
  66. data/test/fixtures/apps/mountable_apps/static.html +1 -0
  67. data/test/fixtures/apps/precompiled_app.rb +19 -0
  68. data/test/fixtures/apps/simple.rb +32 -0
  69. data/test/fixtures/apps/static.rb +10 -0
  70. data/test/fixtures/apps/system.rb +13 -0
  71. data/test/fixtures/apps/system_class_methods_demo.rb +7 -0
  72. data/test/fixtures/apps/system_instance_methods_demo.rb +7 -0
  73. data/test/fixtures/dependencies/a.rb +9 -0
  74. data/test/fixtures/dependencies/b.rb +4 -0
  75. data/test/fixtures/dependencies/c.rb +1 -0
  76. data/test/fixtures/dependencies/circular/e.rb +13 -0
  77. data/test/fixtures/dependencies/circular/f.rb +2 -0
  78. data/test/fixtures/dependencies/circular/g.rb +2 -0
  79. data/test/fixtures/dependencies/d.rb +4 -0
  80. data/test/fixtures/reloadable_apps/external/app/app.rb +6 -0
  81. data/test/fixtures/reloadable_apps/external/app/controllers/base.rb +6 -0
  82. data/test/fixtures/reloadable_apps/main/app.rb +10 -0
  83. data/test/helper.rb +30 -0
  84. data/test/test_application.rb +185 -0
  85. data/test/test_core.rb +93 -0
  86. data/test/test_csrf_protection.rb +208 -0
  87. data/test/test_dependencies.rb +57 -0
  88. data/test/test_filters.rb +389 -0
  89. data/test/test_flash.rb +168 -0
  90. data/test/test_locale.rb +21 -0
  91. data/test/test_logger.rb +295 -0
  92. data/test/test_mounter.rb +302 -0
  93. data/test/test_params_protection.rb +195 -0
  94. data/test/test_reloader_complex.rb +74 -0
  95. data/test/test_reloader_external.rb +21 -0
  96. data/test/test_reloader_simple.rb +101 -0
  97. data/test/test_reloader_system.rb +113 -0
  98. data/test/test_restful_routing.rb +33 -0
  99. data/test/test_router.rb +281 -0
  100. data/test/test_routing.rb +2328 -0
  101. metadata +301 -0
@@ -0,0 +1,129 @@
1
+ begin
2
+ require 'active_support/core_ext/object/deep_dup' # AS 4.1
3
+ rescue LoadError
4
+ require 'active_support/core_ext/hash/deep_dup' # AS >= 3.1
5
+ end
6
+
7
+ module Tennpipes
8
+ ##
9
+ # Tennpipes application module providing means for mass-assignment protection.
10
+ #
11
+ module ParamsProtection
12
+ class << self
13
+ def registered(app)
14
+ included(app)
15
+ end
16
+
17
+ def included(base)
18
+ base.send(:include, InstanceMethods)
19
+ base.extend(ClassMethods)
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ ##
25
+ # Implements filtering of url query params. Can prevent mass-assignment.
26
+ #
27
+ # @example
28
+ # post :update, :params => [:name, :email]
29
+ # post :update, :params => [:name, :id => Integer]
30
+ # post :update, :params => [:name => proc{ |v| v.reverse }]
31
+ # post :update, :params => [:name, :parent => [:name, :position]]
32
+ # post :update, :params => false
33
+ # post :update, :params => true
34
+ # @example
35
+ # params :name, :email, :password => prox{ |v| v.reverse }
36
+ # post :update
37
+ # @example
38
+ # App.controller :accounts, :params => [:name, :position] do
39
+ # post :create
40
+ # post :update, :with => [ :id ], :params => [:name, :position, :addition]
41
+ # get :show, :with => :id, :params => false
42
+ # get :search, :params => true
43
+ # end
44
+ #
45
+ def params(*allowed_params)
46
+ allowed_params = prepare_allowed_params(allowed_params)
47
+ condition do
48
+ @original_params = params.deep_dup
49
+ filter_params!(params, allowed_params)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def prepare_allowed_params(allowed_params)
56
+ param_filter = {}
57
+ allowed_params.each do |key,value|
58
+ case
59
+ when key.kind_of?(Hash) && !value
60
+ param_filter.update(prepare_allowed_params(key))
61
+ when value.kind_of?(Hash) || value.kind_of?(Array)
62
+ param_filter[key.to_s] = prepare_allowed_params(value)
63
+ else
64
+ param_filter[key.to_s] = value == false ? false : (value || true)
65
+ end
66
+ end
67
+ param_filter.freeze
68
+ end
69
+ end
70
+
71
+ module InstanceMethods
72
+ ##
73
+ # Filters a hash of parameters leaving only allowed ones and possibly
74
+ # typecasting and processing the others.
75
+ #
76
+ # @param [Hash] params
77
+ # Parameters to filter.
78
+ # Warning: this hash will be changed by deleting or replacing its values.
79
+ # @param [Hash] allowed_params
80
+ # A hash of allowed keys and value classes or processing procs. Supported
81
+ # scalar classes are: Integer (empty string is cast to nil).
82
+ #
83
+ # @example
84
+ # filter_params!( { "a" => "1", "b" => "abc", "d" => "drop" },
85
+ # { "a" => Integer, "b" => true } )
86
+ # # => { "a" => 1, "b" => "abc" }
87
+ # filter_params!( { "id" => "", "child" => { "name" => "manny" } },
88
+ # { "id" => Integer, "child" => { "name" => proc{ |v| v.camelize } } } )
89
+ # # => { "id" => nil, "child" => { "name" => "Manny" } }
90
+ # filter_params!( { "a" => ["1", "2", "3"] },
91
+ # { "a" => true } )
92
+ # # => { "a" => ["1", "2", "3"] }
93
+ # filter_params!( { "persons" => {"p-1" => { "name" => "manny", "age" => "50" }, "p-2" => { "name" => "richard", "age" => "50" } } },
94
+ # { "persons" => { "name" => true } } )
95
+ # # => { "persons" => {"p-1" => { "name" => "manny" }, "p-2" => { "name" => "richard" } } }
96
+ #
97
+ def filter_params!(params, allowed_params)
98
+ params.each do |key,value|
99
+ type = allowed_params[key]
100
+ next if value.kind_of?(Array) && type
101
+ case
102
+ when type.kind_of?(Hash) && value.kind_of?(Hash)
103
+ if key == key.pluralize && value.values.first.kind_of?(Hash)
104
+ value.each do |array_index,array_value|
105
+ value[array_index] = filter_params!(array_value, type)
106
+ end
107
+ else
108
+ params[key] = filter_params!(value, type)
109
+ end
110
+ when type == Integer
111
+ params[key] = value.empty? ? nil : value.to_i
112
+ when type.kind_of?(Proc)
113
+ params[key] = type.call(value)
114
+ when type == true
115
+ else
116
+ params.delete(key)
117
+ end
118
+ end
119
+ end
120
+
121
+ ##
122
+ # Returns the original unfiltered query parameters hash.
123
+ #
124
+ def original_params
125
+ @original_params || params
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,1002 @@
1
+ require 'tennpipes-assist'
2
+ require 'tennpipes-base/path_router' unless defined?(PathRouter)
3
+ require 'tennpipes-base/ext/sinatra'
4
+ require 'tennpipes-base/filter'
5
+
6
+ module Tennpipes
7
+ ##
8
+ # Tennpipes provides advanced routing definition support to make routes and
9
+ # url generation much easier. This routing system supports named route
10
+ # aliases and easy access to url paths. The benefits of this is that instead
11
+ # of having to hard-code route urls into every area of your application, now
12
+ # we can just define the urls in a single spot and then attach an alias
13
+ # which can be used to refer to the url throughout the application.
14
+ #
15
+ module Routing
16
+ # Defines common content-type alias mappings.
17
+ CONTENT_TYPE_ALIASES = { :htm => :html } unless defined?(CONTENT_TYPE_ALIASES)
18
+ # Defines the available route priorities supporting route deferrals.
19
+ ROUTE_PRIORITY = {:high => 0, :normal => 1, :low => 2} unless defined?(ROUTE_PRIORITY)
20
+
21
+ # Raised when a route was invalid or cannot be processed.
22
+ class UnrecognizedException < RuntimeError; end
23
+
24
+ # Raised when block arity was nonzero and was not same with
25
+ # captured parameter length.
26
+ class BlockArityError < ArgumentError
27
+ def initialize(path, block_arity, required_arity)
28
+ super "route block arity does not match path '#{path}' (#{block_arity} for #{required_arity})"
29
+ end
30
+ end
31
+
32
+ class Parent < String
33
+ attr_reader :map
34
+ attr_reader :optional
35
+ attr_reader :options
36
+
37
+ alias_method :optional?, :optional
38
+
39
+ def initialize(value, options={})
40
+ super(value.to_s)
41
+ @map = options.delete(:map)
42
+ @optional = options.delete(:optional)
43
+ @options = options
44
+ end
45
+ end
46
+
47
+ class << self
48
+ ##
49
+ # Main class that register this extension.
50
+ #
51
+ def registered(app)
52
+ app.send(:include, InstanceMethods)
53
+ app.extend(ClassMethods)
54
+ end
55
+ alias :included :registered
56
+ end
57
+
58
+ # Class methods responsible for enhanced routing for controllers.
59
+ module ClassMethods
60
+ ##
61
+ # Method to organize our routes in a better way.
62
+ #
63
+ # @param [Array] args
64
+ # Controller arguments.
65
+ #
66
+ # @yield []
67
+ # The given block will be used to define the routes within the
68
+ # Controller.
69
+ #
70
+ # @example
71
+ # controller :admin do
72
+ # get :index do; ...; end
73
+ # get :show, :with => :id do; ...; end
74
+ # end
75
+ #
76
+ # url(:admin_index) # => "/admin"
77
+ # url(:admin_show, :id => 1) # "/admin/show/1"
78
+ #
79
+ # @example Using named routes follow the sinatra way:
80
+ # controller "/admin" do
81
+ # get "/index" do; ...; end
82
+ # get "/show/:id" do; ...; end
83
+ # end
84
+ #
85
+ # @example Supply +:provides+ to all controller routes:
86
+ # controller :provides => [:html, :xml, :json] do
87
+ # get :index do; "respond to html, xml and json"; end
88
+ # post :index do; "respond to html, xml and json"; end
89
+ # get :foo do; "respond to html, xml and json"; end
90
+ # end
91
+ #
92
+ # @example Specify parent resources in tennpipes with the +:parent+ option on the controller:
93
+ # controllers :product, :parent => :user do
94
+ # get :index do
95
+ # # url is generated as "/user/#{params[:user_id]}/product"
96
+ # # url_for(:product, :index, :user_id => 5) => "/user/5/product"
97
+ # end
98
+ # get :show, :with => :id do
99
+ # # url is generated as "/user/#{params[:user_id]}/product/show/#{params[:id]}"
100
+ # # url_for(:product, :show, :user_id => 5, :id => 10) => "/user/5/product/show/10"
101
+ # end
102
+ # end
103
+ #
104
+ # @example Specify conditions to run for all routes:
105
+ # controller :conditions => {:protect => true} do
106
+ # def self.protect(protected)
107
+ # condition do
108
+ # halt 403, "No secrets for you!" unless params[:key] == "s3cr3t"
109
+ # end if protected
110
+ # end
111
+ #
112
+ # # This route will only return "secret stuff" if the user goes to
113
+ # # `/private?key=s3cr3t`.
114
+ # get("/private") { "secret stuff" }
115
+ #
116
+ # # And this one, too!
117
+ # get("/also-private") { "secret stuff" }
118
+ #
119
+ # # But you can override the conditions for each route as needed.
120
+ # # This route will be publicly accessible without providing the
121
+ # # secret key.
122
+ # get :index, :protect => false do
123
+ # "Welcome!"
124
+ # end
125
+ # end
126
+ #
127
+ # @example Supply default values:
128
+ # controller :lang => :de do
129
+ # get :index, :map => "/:lang" do; "params[:lang] == :de"; end
130
+ # end
131
+ #
132
+ # In a controller, before and after filters are scoped and don't
133
+ # affect other controllers or the main app.
134
+ # In a controller, layouts are scoped and don't affect other
135
+ # controllers or the main app.
136
+ #
137
+ # @example
138
+ # controller :posts do
139
+ # layout :post
140
+ # before { foo }
141
+ # after { bar }
142
+ # end
143
+ #
144
+ def controller(*args, &block)
145
+ if block_given?
146
+ with_new_options(*args) { instance_eval(&block) }
147
+ else
148
+ include(*args) if extensions.any?
149
+ end
150
+ end
151
+ alias :controllers :controller
152
+
153
+ ##
154
+ # Add a before filter hook.
155
+ #
156
+ # @see #construct_filter
157
+ #
158
+ def before(*args, &block)
159
+ add_filter :before, &(args.empty? ? block : construct_filter(*args, &block))
160
+ end
161
+
162
+ ##
163
+ # Add an after filter hook.
164
+ #
165
+ # @see #construct_filter
166
+ #
167
+ def after(*args, &block)
168
+ add_filter :after, &(args.empty? ? block : construct_filter(*args, &block))
169
+ end
170
+
171
+ ##
172
+ # Adds a filter hook to a request.
173
+ #
174
+ def add_filter(type, &block)
175
+ filters[type] << block
176
+ end
177
+
178
+ ##
179
+ # Creates a filter to process before/after the matching route.
180
+ #
181
+ # @param [Array] args
182
+ #
183
+ # @example We are be able to filter with String path
184
+ # before('/') { 'only to :index' }
185
+ # get(:index} { 'foo' } # => filter match only before this.
186
+ # get(:main) { 'bar' }
187
+ #
188
+ # @example is the same of
189
+ # before(:index) { 'only to :index' }
190
+ # get(:index} { 'foo' } # => filter match only before this.
191
+ # get(:main) { 'bar' }
192
+ #
193
+ # @example it works only for the given controller
194
+ # controller :foo do
195
+ # before(:index) { 'only to for :foo_index' }
196
+ # get(:index} { 'foo' } # => filter match only before this.
197
+ # get(:main) { 'bar' }
198
+ # end
199
+ #
200
+ # controller :bar do
201
+ # before(:index) { 'only to for :bar_index' }
202
+ # get(:index} { 'foo' } # => filter match only before this.
203
+ # get(:main) { 'bar' }
204
+ # end
205
+ #
206
+ # @example if filters based on a symbol or regexp
207
+ # before :index, /main/ do; ... end
208
+ # # => match only path that are +/+ or contains +main+
209
+ #
210
+ # @example filtering everything except an occurrence
211
+ # before :except => :index do; ...; end
212
+ #
213
+ # @example you can also filter using a request param
214
+ # before :agent => /IE/ do; ...; end
215
+ # # => match +HTTP_USER_AGENT+ containing +IE+
216
+ #
217
+ # @see http://www.tennpipesrb.com/guides/controllers#route-filters
218
+ #
219
+ def construct_filter(*args, &block)
220
+ options = args.extract_options!
221
+ if except = options.delete(:except)
222
+ fail "You cannot use :except with other options specified" unless args.empty? && options.empty?
223
+ options = Array(except).extract_options!
224
+ end
225
+ Filter.new(!except, @_controller, options, Array(except || args), &block)
226
+ end
227
+
228
+ ##
229
+ # Provides many parents with shallowing.
230
+ #
231
+ # @param [Symbol] name
232
+ # The parent name.
233
+ #
234
+ # @param [Hash] options
235
+ # Additional options.
236
+ #
237
+ # @example
238
+ # controllers :product do
239
+ # parent :shop, :optional => true, :map => "/my/stand"
240
+ # parent :category, :optional => true
241
+ # get :show, :with => :id do
242
+ # # generated urls:
243
+ # # "/product/show/#{params[:id]}"
244
+ # # "/my/stand/#{params[:shop_id]}/product/show/#{params[:id]}"
245
+ # # "/my/stand/#{params[:shop_id]}/category/#{params[:category_id]}/product/show/#{params[:id]}"
246
+ # # url_for(:product, :show, :id => 10) => "/product/show/10"
247
+ # # url_for(:product, :show, :shop_id => 5, :id => 10) => "/my/stand/5/product/show/10"
248
+ # # url_for(:product, :show, :shop_id => 5, :category_id => 1, :id => 10) => "/my/stand/5/category/1/product/show/10"
249
+ # end
250
+ # end
251
+ #
252
+ def parent(name = nil, options={})
253
+ return super() unless name
254
+ defaults = { :optional => false, :map => name.to_s }
255
+ options = defaults.merge(options)
256
+ @_parent = Array(@_parent) unless @_parent.is_a?(Array)
257
+ @_parent << Parent.new(name, options)
258
+ end
259
+
260
+ ##
261
+ # Using PathRouter, for features and configurations.
262
+ #
263
+ # @example
264
+ # router.add('/greedy/:greed')
265
+ # router.recognize('/simple')
266
+ #
267
+ def router
268
+ @router ||= PathRouter.new
269
+ block_given? ? yield(@router) : @router
270
+ end
271
+ alias :urls :router
272
+
273
+ def compiled_router
274
+ if @deferred_routes
275
+ deferred_routes.each do |routes|
276
+ routes.each do |(route, dest)|
277
+ route.to(&dest)
278
+ route.before_filters.flatten!
279
+ route.after_filters.flatten!
280
+ end
281
+ end
282
+ @deferred_routes = nil
283
+ end
284
+ router
285
+ end
286
+
287
+ def deferred_routes
288
+ @deferred_routes ||= ROUTE_PRIORITY.map{[]}
289
+ end
290
+
291
+ def reset_router!
292
+ @deferred_routes = nil
293
+ router.reset!
294
+ end
295
+
296
+ ##
297
+ # Recognize a given path.
298
+ #
299
+ # @param [String] path
300
+ # Path+Query to parse
301
+ #
302
+ # @return [Symbol, Hash]
303
+ # Returns controller and query params.
304
+ #
305
+ # @example Giving a controller like:
306
+ # controller :foo do
307
+ # get :bar, :map => 'foo-bar-:id'; ...; end
308
+ # end
309
+ #
310
+ # @example You should be able to reverse:
311
+ # MyApp.url(:foo_bar, :id => :mine)
312
+ # # => /foo-bar-mine
313
+ #
314
+ # @example Into this:
315
+ # MyApp.recognize_path('foo-bar-mine')
316
+ # # => [:foo_bar, :id => :mine]
317
+ #
318
+ def recognize_path(path)
319
+ responses = @router.recognize_path(path)
320
+ [responses[0], responses[1]]
321
+ end
322
+
323
+ ##
324
+ # Instance method for url generation.
325
+ #
326
+ # @option options [String] :fragment
327
+ # An addition to url to identify a portion of requested resource (i.e #something).
328
+ # @option options [String] :anchor
329
+ # Synonym for fragment.
330
+ #
331
+ # @example
332
+ # url(:show, :id => 1)
333
+ # url(:show, :name => 'test', :id => 24)
334
+ # url(:show, 1)
335
+ # url(:controller_name, :show, :id => 21)
336
+ # url(:controller_show, :id => 29)
337
+ # url(:index, :fragment => 'comments')
338
+ #
339
+ def url(*args)
340
+ params = args.extract_options!
341
+ fragment = params.delete(:fragment) || params.delete(:anchor)
342
+ path = make_path_with_params(args, value_to_param(params.symbolize_keys))
343
+ rebase_url(fragment ? path << '#' << fragment.to_s : path)
344
+ end
345
+ alias :url_for :url
346
+
347
+ def get(path, *args, &block)
348
+ conditions = @conditions.dup
349
+ route('GET', path, *args, &block)
350
+
351
+ @conditions = conditions
352
+ route('HEAD', path, *args, &block)
353
+ end
354
+
355
+ def put(path, *args, &block) route 'PUT', path, *args, &block end
356
+ def post(path, *args, &block) route 'POST', path, *args, &block end
357
+ def delete(path, *args, &block) route 'DELETE', path, *args, &block end
358
+ def head(path, *args, &block) route 'HEAD', path, *args, &block end
359
+ def options(path, *args, &block) route 'OPTIONS', path, *args, &block end
360
+ def patch(path, *args, &block) route 'PATCH', path, *args, &block end
361
+ def link(path, *args, &block) route 'LINK', path, *args, &block end
362
+ def unlink(path, *args, &block) route 'UNLINK', path, *args, &block end
363
+
364
+ def rebase_url(url)
365
+ if url.start_with?('/')
366
+ new_url = ''
367
+ new_url << conform_uri(ENV['RACK_BASE_URI']) if ENV['RACK_BASE_URI']
368
+ new_url << conform_uri(uri_root) if defined?(uri_root)
369
+ new_url << url
370
+ else
371
+ url.blank? ? '/' : url
372
+ end
373
+ end
374
+
375
+ ##
376
+ # Processes the existing path and prepends the 'parent' parameters onto the route
377
+ # Used for calculating path in route method.
378
+ #
379
+ def process_path_for_parent_params(path, parent_params)
380
+ parent_prefix = parent_params.flatten.compact.uniq.map do |param|
381
+ map = (param.respond_to?(:map) && param.map ? param.map : param.to_s)
382
+ part = "#{map}/:#{param.to_s.singularize}_id/"
383
+ part = "(#{part})?" if param.respond_to?(:optional) && param.optional?
384
+ part
385
+ end
386
+
387
+ [parent_prefix, path].flatten.join("")
388
+ end
389
+
390
+ private
391
+
392
+ CONTROLLER_OPTIONS = [ :parent, :provides, :use_format, :cache, :expires, :map, :conditions, :accepts, :params ].freeze
393
+
394
+ # Saves controller options, yields the block, restores controller options.
395
+ def with_new_options(*args)
396
+ options = args.extract_options!
397
+
398
+ CONTROLLER_OPTIONS.each{ |key| replace_instance_variable("@_#{key}", options.delete(key)) }
399
+ replace_instance_variable(:@_controller, args)
400
+ replace_instance_variable(:@_defaults, options)
401
+ replace_instance_variable(:@filters, :before => @filters[:before].dup, :after => @filters[:after].dup)
402
+ replace_instance_variable(:@layout, nil)
403
+
404
+ yield
405
+
406
+ @original_instance.each do |key, value|
407
+ instance_variable_set(key, value)
408
+ end
409
+ end
410
+
411
+ # Sets instance variable by name and saves the original value in @original_instance hash
412
+ def replace_instance_variable(name, value)
413
+ @original_instance ||= {}
414
+ @original_instance[name] = instance_variable_get(name)
415
+ instance_variable_set(name, value)
416
+ end
417
+
418
+ # Searches compiled router for a path responding to args and makes a path with params.
419
+ def make_path_with_params(args, params)
420
+ names, params_array = args.partition{ |arg| arg.is_a?(Symbol) }
421
+ name = names[0, 2].join(" ").to_sym
422
+ compiled_router.path(name, *(params_array << params))
423
+ rescue PathRouter::InvalidRouteException
424
+ raise Tennpipes::Routing::UnrecognizedException, "Route mapping for url(#{name.inspect}) could not be found"
425
+ end
426
+
427
+ # Parse params from the url method
428
+ def value_to_param(object)
429
+ case object
430
+ when Array
431
+ object.map { |item| value_to_param(item) }.compact
432
+ when Hash
433
+ object.inject({}) do |all, (key, value)|
434
+ next all if value.nil?
435
+ all[key] = value_to_param(value)
436
+ all
437
+ end
438
+ when nil
439
+ else
440
+ object.respond_to?(:to_param) ? object.to_param : object
441
+ end
442
+ end
443
+
444
+ # Add prefix slash if its not present and remove trailing slashes.
445
+ def conform_uri(uri_string)
446
+ uri_string.gsub(/^(?!\/)(.*)/, '/\1').gsub(/[\/]+$/, '')
447
+ end
448
+
449
+ ##
450
+ # Rewrite default routes.
451
+ #
452
+ # @example
453
+ # get :index # => "/"
454
+ # get :index, "/" # => "/"
455
+ # get :index, :map => "/" # => "/"
456
+ # get :show, "/show-me" # => "/show-me"
457
+ # get :show, :map => "/show-me" # => "/show-me"
458
+ # get "/foo/bar" # => "/show"
459
+ # get :index, :parent => :user # => "/user/:user_id/index"
460
+ # get :show, :with => :id, :parent => :user # => "/user/:user_id/show/:id"
461
+ # get :show, :with => :id # => "/show/:id"
462
+ # get [:show, :id] # => "/show/:id"
463
+ # get :show, :with => [:id, :name] # => "/show/:id/:name"
464
+ # get [:show, :id, :name] # => "/show/:id/:name"
465
+ # get :list, :provides => :js # => "/list.{:format,js)"
466
+ # get :list, :provides => :any # => "/list(.:format)"
467
+ # get :list, :provides => [:js, :json] # => "/list.{!format,js|json}"
468
+ # get :list, :provides => [:html, :js, :json] # => "/list(.{!format,js|json})"
469
+ # get :list, :priority => :low # Defers route to be last
470
+ # get /pattern/, :name => :foo, :generate_with => '/foo' # Generates :foo as /foo
471
+ def route(verb, path, *args, &block)
472
+ options = case args.size
473
+ when 2
474
+ args.last.merge(:map => args.first)
475
+ when 1
476
+ map = args.shift if args.first.is_a?(String)
477
+ if args.first.is_a?(Hash)
478
+ map ? args.first.merge(:map => map) : args.first
479
+ else
480
+ {:map => map || args.first}
481
+ end
482
+ when 0
483
+ {}
484
+ else raise
485
+ end
486
+
487
+ route_options = options.dup
488
+ route_options[:provides] = @_provides if @_provides
489
+ route_options[:accepts] = @_accepts if @_accepts
490
+ route_options[:params] = @_params unless @_params.nil? || route_options.include?(:params)
491
+
492
+ # Add Sinatra condition to check rack-protection failure.
493
+ if protect_from_csrf && (report_csrf_failure || allow_disabled_csrf)
494
+ unless route_options.has_key?(:csrf_protection)
495
+ route_options[:csrf_protection] = true
496
+ end
497
+ end
498
+
499
+ path, *route_options[:with] = path if path.is_a?(Array)
500
+ action = path
501
+ path, name, route_parents, options, route_options = *parse_route(path, route_options, verb)
502
+ options.reverse_merge!(@_conditions) if @_conditions
503
+
504
+ method_name = "#{verb} #{path}"
505
+ unbound_method = generate_method(method_name, &block)
506
+
507
+ block_arity = block.arity
508
+ block = if block_arity == 0
509
+ proc{ |request, _| unbound_method.bind(request).call }
510
+ else
511
+ proc{ |request, block_params| unbound_method.bind(request).call(*block_params) }
512
+ end
513
+
514
+ invoke_hook(:route_added, verb, path, block)
515
+
516
+ path[0, 0] = "/" if path == "(.:format)?"
517
+ route = router.add(verb, path, route_options)
518
+ route.name = name if name
519
+ route.action = action
520
+ priority_name = options.delete(:priority) || :normal
521
+ priority = ROUTE_PRIORITY[priority_name] or raise("Priority #{priority_name} not recognized, try #{ROUTE_PRIORITY.keys.join(', ')}")
522
+ route.cache = options.key?(:cache) ? options.delete(:cache) : @_cache
523
+ route.cache_expires = options.key?(:expires) ? options.delete(:expires) : @_expires
524
+ route.parent = route_parents ? (route_parents.count == 1 ? route_parents.first : route_parents) : route_parents
525
+ route.host = options.delete(:host) if options.key?(:host)
526
+ route.user_agent = options.delete(:agent) if options.key?(:agent)
527
+ if options.key?(:default_values)
528
+ defaults = options.delete(:default_values)
529
+ #route.options[:default_values] = defaults if defaults
530
+ route.default_values = defaults if defaults
531
+ end
532
+ options.delete_if do |option, captures|
533
+ if route.significant_variable_names.include?(option)
534
+ route.capture[option] = Array(captures).first
535
+ true
536
+ end
537
+ end
538
+
539
+ # Add Sinatra conditions.
540
+ options.each do |option, _args|
541
+ option = :provides_format if option == :provides
542
+ route.respond_to?(option) ? route.send(option, *_args) : send(option, *_args)
543
+ end
544
+ conditions, @conditions = @conditions, []
545
+ route.custom_conditions.concat(conditions)
546
+
547
+ invoke_hook(:tennpipes_route_added, route, verb, path, args, options, block)
548
+
549
+ block_parameter_length = route.block_parameter_length
550
+ if block_arity > 0 && block_parameter_length != block_arity
551
+ fail BlockArityError.new(route.path, block_arity, block_parameter_length)
552
+ end
553
+
554
+ # Add Application defaults.
555
+ route.before_filters << @filters[:before]
556
+ route.after_filters << @filters[:after]
557
+ if @_controller
558
+ route.use_layout = @layout
559
+ route.controller = Array(@_controller).join('/')
560
+ end
561
+
562
+ deferred_routes[priority] << [route, block]
563
+
564
+ route
565
+ end
566
+
567
+ ##
568
+ # Returns the final parsed route details (modified to reflect all
569
+ # Tennpipes options) given the raw route. Raw route passed in could be
570
+ # a named alias or a string and is parsed to reflect provides formats,
571
+ # controllers, parents, 'with' parameters, and other options.
572
+ #
573
+ def parse_route(path, options, verb)
574
+ route_options = {}
575
+
576
+ if options[:params] == true
577
+ options.delete(:params)
578
+ elsif options.include?(:params)
579
+ options[:params] ||= []
580
+ options[:params] |= Array(options[:with]) if options[:with]
581
+ end
582
+
583
+ # We need check if path is a symbol, if that it's a named route.
584
+ map = options.delete(:map)
585
+
586
+ # path i.e :index or :show
587
+ if path.kind_of?(Symbol)
588
+ name = path
589
+ path = map ? map.dup : (path == :index ? '/' : path.to_s)
590
+ end
591
+
592
+ # Build our controller
593
+ controller = Array(@_controller).map(&:to_s)
594
+
595
+ case path
596
+ when String # path i.e "/index" or "/show"
597
+ # Now we need to parse our 'with' params
598
+ if with_params = options.delete(:with)
599
+ path = process_path_for_with_params(path, with_params)
600
+ end
601
+
602
+ # Now we need to parse our provides
603
+ options.delete(:provides) if options[:provides].nil?
604
+
605
+ options.delete(:accepts) if options[:accepts].nil?
606
+
607
+ if @_use_format || options[:provides]
608
+ process_path_for_provides(path)
609
+ # options[:add_match_with] ||= {}
610
+ # options[:add_match_with][:format] = /[^\.]+/
611
+ end
612
+
613
+ absolute_map = map && map[0] == ?/
614
+
615
+ unless controller.empty?
616
+ # Now we need to add our controller path only if not mapped directly
617
+ if map.blank? and !absolute_map
618
+ controller_path = controller.join("/")
619
+ path.gsub!(%r{^\(/\)|/\?}, "")
620
+ path = File.join(controller_path, path) unless @_map
621
+ end
622
+ end
623
+
624
+ # Now we need to parse our 'parent' params and parent scope.
625
+ if !absolute_map and parent_params = options.delete(:parent) || @_parent
626
+ parent_params = (Array(@_parent) + Array(parent_params)).uniq
627
+ path = process_path_for_parent_params(path, parent_params)
628
+ end
629
+
630
+ # Add any controller level map to the front of the path.
631
+ path = "#{@_map}/#{path}".squeeze('/') unless absolute_map or @_map.blank?
632
+
633
+ # Small reformats
634
+ path.gsub!(%r{/\?$}, '(/)') # Remove index path
635
+ path.gsub!(%r{//$}, '/') # Remove index path
636
+ path[0,0] = "/" if path !~ %r{^\(?/} # Paths must start with a /
637
+ path.sub!(%r{/(\))?$}, '\\1') if path != "/" # Remove latest trailing delimiter
638
+ path.gsub!(/\/(\(\.|$)/, '\\1') # Remove trailing slashes
639
+ path.squeeze!('/')
640
+ when Regexp
641
+ route_options[:path_for_generation] = options.delete(:generate_with) if options.key?(:generate_with)
642
+ end
643
+
644
+ name = options.delete(:route_name) if name.nil? && options.key?(:route_name)
645
+ name = options.delete(:name) if name.nil? && options.key?(:name)
646
+ if name
647
+ controller_name = controller.join("_")
648
+ name = "#{controller_name} #{name}".to_sym unless controller_name.blank?
649
+ end
650
+
651
+ # Merge in option defaults.
652
+ options.reverse_merge!(:default_values => @_defaults)
653
+
654
+ [path, name, parent_params, options, route_options]
655
+ end
656
+
657
+ ##
658
+ # Processes the existing path and appends the 'with' parameters onto the route
659
+ # Used for calculating path in route method.
660
+ #
661
+ def process_path_for_with_params(path, with_params)
662
+ File.join(path, Array(with_params).map do |step|
663
+ step.kind_of?(String) ? step : step.inspect
664
+ end.join("/"))
665
+ end
666
+
667
+ ##
668
+ # Processes the existing path and appends the 'format' suffix onto the route.
669
+ # Used for calculating path in route method.
670
+ #
671
+ def process_path_for_provides(path)
672
+ path << "(.:format)?" unless path[-11, 11] == '(.:format)?'
673
+ end
674
+
675
+ ##
676
+ # Allows routing by MIME-types specified in the URL or ACCEPT header.
677
+ #
678
+ # By default, if a non-provided mime-type is specified in a URL, the
679
+ # route will not match an thus return a 404.
680
+ #
681
+ # Setting the :treat_format_as_accept option to true allows treating
682
+ # missing mime types specified in the URL as if they were specified
683
+ # in the ACCEPT header and thus return 406.
684
+ #
685
+ # If no type is specified, the first in the provides-list will be
686
+ # returned.
687
+ #
688
+ # @example
689
+ # get "/a", :provides => [:html, :js]
690
+ # # => GET /a => :html
691
+ # # => GET /a.js => :js
692
+ # # => GET /a.xml => 404
693
+ #
694
+ # get "/b", :provides => [:html]
695
+ # # => GET /b; ACCEPT: html => html
696
+ # # => GET /b; ACCEPT: js => 406
697
+ #
698
+ # enable :treat_format_as_accept
699
+ # get "/c", :provides => [:html, :js]
700
+ # # => GET /c.xml => 406
701
+ #
702
+ def provides(*types)
703
+ @_use_format = true
704
+ provides_format(*types)
705
+ end
706
+
707
+ def provides_format(*types)
708
+ mime_types = types.map{ |type| mime_type(CONTENT_TYPE_ALIASES[type] || type) }
709
+ condition do
710
+ return provides_format?(types, params[:format].to_sym) if params[:format]
711
+
712
+ accepts = request.accept.map(&:to_str)
713
+ # Per rfc2616-sec14:
714
+ # Assume */* if no ACCEPT header is given.
715
+ catch_all = accepts.delete("*/*")
716
+
717
+ return provides_any?(accepts) if types.include?(:any)
718
+
719
+ accepts = accepts.empty? ? mime_types.slice(0,1) : (accepts & mime_types)
720
+
721
+ type = accepts.first && mime_symbol(accepts.first)
722
+ type ||= catch_all && types.first
723
+
724
+ accept_format = CONTENT_TYPE_ALIASES[type] || type
725
+ if types.include?(accept_format)
726
+ content_type(accept_format || :html, :charset => 'utf-8')
727
+ else
728
+ halt 406 unless catch_all
729
+ false
730
+ end
731
+ end
732
+ end
733
+
734
+ ##
735
+ # Allows routing by Media type.
736
+ #
737
+ # @example
738
+ # get "/a", :accepts => [:html, :js]
739
+ # # => GET /a CONTENT_TYPE text/html => :html
740
+ # # => GET /a CONTENT_TYPE application/javascript => :js
741
+ # # => GET /a CONTENT_TYPE application/xml => 406
742
+ #
743
+ def accepts(*types)
744
+ mime_types = types.map{ |type| mime_type(CONTENT_TYPE_ALIASES[type] || type) }
745
+ condition do
746
+ halt 406 unless mime_types.include?(request.media_type)
747
+ content_type(mime_symbol(request.media_type), :charset => 'utf-8')
748
+ end
749
+ end
750
+
751
+ ##
752
+ # Implements checking for rack-protection failure flag when
753
+ # `report_csrf_failure` is enabled.
754
+ #
755
+ # @example
756
+ # post("/", :csrf_protection => false)
757
+ #
758
+ def csrf_protection(enabled)
759
+ return unless enabled
760
+ condition do
761
+ if request.env['protection.csrf.failed']
762
+ message = settings.protect_from_csrf.kind_of?(Hash) && settings.protect_from_csrf[:message] || 'Forbidden'
763
+ halt(403, message)
764
+ end
765
+ end
766
+ end
767
+ end
768
+
769
+ ##
770
+ # Instance methods related to recognizing and processing routes and serving static files.
771
+ #
772
+ module InstanceMethods
773
+ ##
774
+ # Instance method for URL generation.
775
+ #
776
+ # @example
777
+ # url(:show, :id => 1)
778
+ # url(:show, :name => :test)
779
+ # url(:show, 1)
780
+ # url("/foo", false, false)
781
+ #
782
+ # @see Tennpipes::Routing::ClassMethods#url
783
+ #
784
+ def url(*args)
785
+ if args.first.is_a?(String)
786
+ url_path = settings.rebase_url(args.shift)
787
+ if args.empty?
788
+ url_path
789
+ else
790
+ # Delegate sinatra-style urls to Sinatra. Ex: url("/foo", false, false)
791
+ # http://www.sinatrarb.com/intro#Generating%20URLs
792
+ super url_path, *args
793
+ end
794
+ else
795
+ # Delegate to Tennpipes named route URL generation.
796
+ settings.url(*args)
797
+ end
798
+ end
799
+ alias :url_for :url
800
+
801
+ ##
802
+ # Returns absolute url. Calls Sinatra::Helpers#uri to generate protocol version, hostname and port.
803
+ #
804
+ # @example
805
+ # absolute_url(:show, :id => 1) # => http://example.com/show?id=1
806
+ # absolute_url(:show, 24) # => https://example.com/admin/show/24
807
+ # absolute_url('/foo/bar') # => https://example.com/admin/foo/bar
808
+ # absolute_url('baz') # => https://example.com/admin/foo/baz
809
+ #
810
+ def absolute_url(*args)
811
+ url_path = args.shift
812
+ if url_path.is_a?(String) && !url_path.start_with?('/')
813
+ url_path = request.env['PATH_INFO'].rpartition('/').first << '/' << url_path
814
+ end
815
+ uri url(url_path, *args), true, false
816
+ end
817
+
818
+ def recognize_path(path)
819
+ settings.recognize_path(path)
820
+ end
821
+
822
+ ##
823
+ # Returns the current path within a route from specified +path_params+.
824
+ #
825
+ def current_path(*path_params)
826
+ if path_params.last.is_a?(Hash)
827
+ path_params[-1] = params.merge(path_params[-1].with_indifferent_access)
828
+ else
829
+ path_params << params
830
+ end
831
+
832
+ path_params[-1] = path_params[-1].symbolize_keys
833
+ @route.path(*path_params)
834
+ end
835
+
836
+ ##
837
+ # Returns the current route
838
+ #
839
+ # @example
840
+ # -if route.controller == :press
841
+ # %li=show_article
842
+ #
843
+ def route
844
+ @route
845
+ end
846
+
847
+ ##
848
+ # This is mostly just a helper so request.path_info isn't changed when
849
+ # serving files from the public directory.
850
+ #
851
+ def static_file?(path_info)
852
+ return unless public_dir = settings.public_folder
853
+ public_dir = File.expand_path(public_dir)
854
+ path = File.expand_path(public_dir + unescape(path_info))
855
+ return unless path.start_with?(public_dir)
856
+ return unless File.file?(path)
857
+ return path
858
+ end
859
+
860
+ #
861
+ # Method for deliver static files.
862
+ #
863
+ def static!(options = {})
864
+ if path = static_file?(request.path_info)
865
+ env['sinatra.static_file'] = path
866
+ cache_control(*settings.static_cache_control) if settings.static_cache_control?
867
+ send_file(path, options)
868
+ end
869
+ end
870
+
871
+ ##
872
+ # Return the request format, this is useful when we need to respond to
873
+ # a given Content-Type.
874
+ #
875
+ # @param [Symbol, nil] type
876
+ #
877
+ # @param [Hash] params
878
+ #
879
+ # @example
880
+ # get :index, :provides => :any do
881
+ # case content_type
882
+ # when :js then ...
883
+ # when :json then ...
884
+ # when :html then ...
885
+ # end
886
+ # end
887
+ #
888
+ def content_type(type=nil, params={})
889
+ return @_content_type unless type
890
+ params.delete(:charset) if type == :json
891
+ super(type, params)
892
+ @_content_type = type
893
+ end
894
+
895
+ private
896
+
897
+ def provides_any?(formats)
898
+ accepted_format = formats.first
899
+ type = accepted_format ? mime_symbol(accepted_format) : :html
900
+ content_type(CONTENT_TYPE_ALIASES[type] || type, :charset => 'utf-8')
901
+ end
902
+
903
+ def provides_format?(types, format)
904
+ if ([:any, format] & types).empty?
905
+ # Per rfc2616-sec14:
906
+ # Answer with 406 if accept is given but types to not match any provided type.
907
+ halt 406 if settings.respond_to?(:treat_format_as_accept) && settings.treat_format_as_accept
908
+ false
909
+ else
910
+ content_type(format || :html, :charset => 'utf-8')
911
+ end
912
+ end
913
+
914
+ def mime_symbol(media_type)
915
+ ::Rack::Mime::MIME_TYPES.key(media_type).sub(/\./,'').to_sym
916
+ end
917
+
918
+ def filter!(type, base=settings)
919
+ base.filters[type].each { |block| instance_eval(&block) }
920
+ end
921
+
922
+ def dispatch!
923
+ invoke do
924
+ static! if settings.static? && (request.get? || request.head?)
925
+ route!
926
+ end
927
+ rescue ::Exception => boom
928
+ filter! :before if boom.kind_of? ::Sinatra::NotFound
929
+ invoke { @boom_handled = handle_exception!(boom) }
930
+ ensure
931
+ @boom_handled or begin
932
+ filter! :after unless env['sinatra.static_file']
933
+ rescue ::Exception => boom
934
+ invoke { handle_exception!(boom) } unless @env['sinatra.error']
935
+ end
936
+ end
937
+
938
+ def route!(base = settings, pass_block = nil)
939
+ Thread.current['tennpipes.instance'] = self
940
+ first_time = true
941
+
942
+ routes = base.compiled_router.call(@request) do |route, params|
943
+ next if route.user_agent && !(route.user_agent =~ @request.user_agent)
944
+ original_params, parent_layout = @params.dup, @layout
945
+ returned_pass_block = invoke_route(route, params, first_time)
946
+ pass_block = returned_pass_block if returned_pass_block
947
+ first_time = false if first_time
948
+ @params, @layout = original_params, parent_layout
949
+ end
950
+
951
+ if routes.present?
952
+ verb = request.request_method
953
+ candidacies, allows = routes.partition{|route| route.verb == verb }
954
+ if candidacies.empty?
955
+ response["Allows"] = allows.map(&:verb).join(", ")
956
+ halt 405
957
+ end
958
+ end
959
+
960
+ if base.superclass.respond_to?(:router)
961
+ route!(base.superclass, pass_block)
962
+ return
963
+ end
964
+
965
+ route_eval(&pass_block) if pass_block
966
+ route_missing
967
+ end
968
+
969
+ def invoke_route(route, params, first_time)
970
+ @_response_buffer = nil
971
+ @route = request.route_obj = route
972
+ captured_params = captures_from_params(params)
973
+
974
+ @params.merge!(params) if params.kind_of?(Hash)
975
+ @params.merge!(:captures => captured_params) if !captured_params.empty? && route.path.is_a?(Regexp)
976
+
977
+ filter! :before if first_time
978
+
979
+ catch(:pass) do
980
+ begin
981
+ (route.before_filters - settings.filters[:before]).each{|block| instance_eval(&block) }
982
+ @layout = route.use_layout if route.use_layout
983
+ route.custom_conditions.each {|block| pass if block.bind(self).call == false }
984
+ route_response = route.block[self, captured_params]
985
+ @_response_buffer = route_response.instance_of?(Array) ? route_response.last : route_response
986
+ halt(route_response)
987
+ ensure
988
+ (route.after_filters - settings.filters[:after]).each {|block| instance_eval(&block) }
989
+ end
990
+ end
991
+ end
992
+
993
+ def captures_from_params(params)
994
+ if params[:captures].instance_of?(Array) && params[:captures].present?
995
+ params.delete(:captures)
996
+ else
997
+ params.values_at(*route.matcher.names).flatten
998
+ end
999
+ end
1000
+ end
1001
+ end
1002
+ end