actionpack 4.1.16 → 4.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +163 -690
  3. data/README.rdoc +7 -2
  4. data/lib/abstract_controller/base.rb +16 -6
  5. data/lib/abstract_controller/callbacks.rb +28 -51
  6. data/lib/abstract_controller/helpers.rb +0 -3
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/rendering.rb +1 -7
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller.rb +1 -0
  11. data/lib/action_controller/base.rb +2 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/caching/fragments.rb +7 -1
  14. data/lib/action_controller/log_subscriber.rb +26 -25
  15. data/lib/action_controller/metal.rb +11 -7
  16. data/lib/action_controller/metal/conditional_get.rb +31 -6
  17. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  18. data/lib/action_controller/metal/force_ssl.rb +1 -1
  19. data/lib/action_controller/metal/head.rb +2 -0
  20. data/lib/action_controller/metal/http_authentication.rb +3 -15
  21. data/lib/action_controller/metal/instrumentation.rb +4 -7
  22. data/lib/action_controller/metal/live.rb +57 -6
  23. data/lib/action_controller/metal/mime_responds.rb +17 -227
  24. data/lib/action_controller/metal/redirecting.rb +14 -8
  25. data/lib/action_controller/metal/renderers.rb +19 -3
  26. data/lib/action_controller/metal/rendering.rb +2 -6
  27. data/lib/action_controller/metal/request_forgery_protection.rb +75 -7
  28. data/lib/action_controller/metal/streaming.rb +1 -1
  29. data/lib/action_controller/metal/strong_parameters.rb +111 -11
  30. data/lib/action_controller/metal/url_for.rb +11 -12
  31. data/lib/action_controller/model_naming.rb +1 -1
  32. data/lib/action_controller/railtie.rb +4 -0
  33. data/lib/action_controller/test_case.rb +87 -75
  34. data/lib/action_dispatch/http/cache.rb +1 -1
  35. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  36. data/lib/action_dispatch/http/headers.rb +43 -9
  37. data/lib/action_dispatch/http/mime_negotiation.rb +10 -4
  38. data/lib/action_dispatch/http/mime_type.rb +2 -16
  39. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  40. data/lib/action_dispatch/http/parameters.rb +11 -26
  41. data/lib/action_dispatch/http/request.rb +30 -10
  42. data/lib/action_dispatch/http/response.rb +52 -17
  43. data/lib/action_dispatch/http/upload.rb +3 -8
  44. data/lib/action_dispatch/http/url.rb +87 -70
  45. data/lib/action_dispatch/journey/formatter.rb +18 -17
  46. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  47. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  48. data/lib/action_dispatch/journey/gtg/transition_table.rb +18 -26
  49. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  50. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  51. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  52. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  53. data/lib/action_dispatch/journey/parser.rb +52 -60
  54. data/lib/action_dispatch/journey/parser.y +11 -10
  55. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  56. data/lib/action_dispatch/journey/route.rb +3 -18
  57. data/lib/action_dispatch/journey/router.rb +34 -65
  58. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  59. data/lib/action_dispatch/journey/routes.rb +0 -4
  60. data/lib/action_dispatch/journey/visitors.rb +81 -92
  61. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  62. data/lib/action_dispatch/middleware/cookies.rb +27 -31
  63. data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -3
  64. data/lib/action_dispatch/middleware/exception_wrapper.rb +19 -17
  65. data/lib/action_dispatch/middleware/flash.rb +7 -4
  66. data/lib/action_dispatch/middleware/public_exceptions.rb +13 -8
  67. data/lib/action_dispatch/middleware/remote_ip.rb +3 -3
  68. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  69. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  70. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  71. data/lib/action_dispatch/middleware/static.rb +22 -23
  72. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +22 -18
  73. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +36 -8
  74. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  75. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  76. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  77. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  78. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  79. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  80. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +119 -63
  81. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  82. data/lib/action_dispatch/routing/inspector.rb +4 -11
  83. data/lib/action_dispatch/routing/mapper.rb +399 -278
  84. data/lib/action_dispatch/routing/polymorphic_routes.rb +190 -78
  85. data/lib/action_dispatch/routing/redirection.rb +10 -12
  86. data/lib/action_dispatch/routing/route_set.rb +224 -177
  87. data/lib/action_dispatch/routing/url_for.rb +9 -4
  88. data/lib/action_dispatch/testing/assertions.rb +11 -7
  89. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  90. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  91. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  92. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  93. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  94. data/lib/action_dispatch/testing/integration.rb +15 -18
  95. data/lib/action_dispatch/testing/test_request.rb +1 -1
  96. data/lib/action_dispatch/testing/test_response.rb +5 -1
  97. data/lib/action_pack/gem_version.rb +3 -3
  98. metadata +57 -15
  99. data/lib/action_controller/metal/responder.rb +0 -297
@@ -101,118 +101,230 @@ module ActionDispatch
101
101
  # polymorphic_url(Comment) # same as comments_url()
102
102
  #
103
103
  def polymorphic_url(record_or_hash_or_array, options = {})
104
- if record_or_hash_or_array.kind_of?(Array)
105
- record_or_hash_or_array = record_or_hash_or_array.compact
106
- if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
107
- proxy = record_or_hash_or_array.shift
108
- end
109
- record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
104
+ if Hash === record_or_hash_or_array
105
+ options = record_or_hash_or_array.merge(options)
106
+ record = options.delete :id
107
+ return polymorphic_url record, options
110
108
  end
111
109
 
112
- record = extract_record(record_or_hash_or_array)
113
- record = convert_to_model(record)
114
-
115
- args = Array === record_or_hash_or_array ?
116
- record_or_hash_or_array.dup :
117
- [ record_or_hash_or_array ]
110
+ opts = options.dup
111
+ action = opts.delete :action
112
+ type = opts.delete(:routing_type) || :url
118
113
 
119
- inflection = if options[:action] && options[:action].to_s == "new"
120
- args.pop
121
- :singular
122
- elsif (record.respond_to?(:persisted?) && !record.persisted?)
123
- args.pop
124
- :plural
125
- elsif record.is_a?(Class)
126
- args.pop
127
- :plural
128
- else
129
- :singular
130
- end
114
+ HelperMethodBuilder.polymorphic_method self,
115
+ record_or_hash_or_array,
116
+ action,
117
+ type,
118
+ opts
131
119
 
132
- args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
133
- named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
134
-
135
- url_options = options.except(:action, :routing_type)
136
- unless url_options.empty?
137
- args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
138
- end
139
-
140
- args.collect! { |a| convert_to_model(a) }
141
-
142
- (proxy || self).send(named_route, *args)
143
120
  end
144
121
 
145
122
  # Returns the path component of a URL for the given record. It uses
146
123
  # <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
147
124
  def polymorphic_path(record_or_hash_or_array, options = {})
148
- polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
125
+ if Hash === record_or_hash_or_array
126
+ options = record_or_hash_or_array.merge(options)
127
+ record = options.delete :id
128
+ return polymorphic_path record, options
129
+ end
130
+
131
+ opts = options.dup
132
+ action = opts.delete :action
133
+ type = :path
134
+
135
+ HelperMethodBuilder.polymorphic_method self,
136
+ record_or_hash_or_array,
137
+ action,
138
+ type,
139
+ opts
149
140
  end
150
141
 
142
+
151
143
  %w(edit new).each do |action|
152
144
  module_eval <<-EOT, __FILE__, __LINE__ + 1
153
- def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
154
- polymorphic_url( # polymorphic_url(
155
- record_or_hash, # record_or_hash,
156
- options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
157
- end # end
158
- #
159
- def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
160
- polymorphic_url( # polymorphic_url(
161
- record_or_hash, # record_or_hash,
162
- options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
163
- end # end
145
+ def #{action}_polymorphic_url(record_or_hash, options = {})
146
+ polymorphic_url_for_action("#{action}", record_or_hash, options)
147
+ end
148
+
149
+ def #{action}_polymorphic_path(record_or_hash, options = {})
150
+ polymorphic_path_for_action("#{action}", record_or_hash, options)
151
+ end
164
152
  EOT
165
153
  end
166
154
 
167
155
  private
168
- def action_prefix(options)
169
- options[:action] ? "#{options[:action]}_" : ''
156
+
157
+ def polymorphic_url_for_action(action, record_or_hash, options)
158
+ polymorphic_url(record_or_hash, options.merge(:action => action))
159
+ end
160
+
161
+ def polymorphic_path_for_action(action, record_or_hash, options)
162
+ options = options.merge(:action => action, :routing_type => :path)
163
+ polymorphic_path(record_or_hash, options)
164
+ end
165
+
166
+ class HelperMethodBuilder # :nodoc:
167
+ CACHE = { 'path' => {}, 'url' => {} }
168
+
169
+ def self.get(action, type)
170
+ type = type.to_s
171
+ CACHE[type].fetch(action) { build action, type }
172
+ end
173
+
174
+ def self.url; CACHE['url'.freeze][nil]; end
175
+ def self.path; CACHE['path'.freeze][nil]; end
176
+
177
+ def self.build(action, type)
178
+ prefix = action ? "#{action}_" : ""
179
+ suffix = type
180
+ if action.to_s == 'new'
181
+ HelperMethodBuilder.singular prefix, suffix
182
+ else
183
+ HelperMethodBuilder.plural prefix, suffix
184
+ end
185
+ end
186
+
187
+ def self.singular(prefix, suffix)
188
+ new(->(name) { name.singular_route_key }, prefix, suffix)
170
189
  end
171
190
 
172
- def routing_type(options)
173
- options[:routing_type] || :url
191
+ def self.plural(prefix, suffix)
192
+ new(->(name) { name.route_key }, prefix, suffix)
174
193
  end
175
194
 
176
- def build_named_route_call(records, inflection, options = {})
177
- if records.is_a?(Array)
178
- record = records.pop
179
- route = records.map do |parent|
180
- if parent.is_a?(Symbol) || parent.is_a?(String)
181
- parent
182
- else
183
- model_name_from_record_or_class(parent).singular_route_key
184
- end
195
+ def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
196
+ builder = get action, type
197
+
198
+ case record_or_hash_or_array
199
+ when Array
200
+ if record_or_hash_or_array.empty? || record_or_hash_or_array.include?(nil)
201
+ raise ArgumentError, "Nil location provided. Can't build URI."
202
+ end
203
+ if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
204
+ recipient = record_or_hash_or_array.shift
185
205
  end
206
+
207
+ method, args = builder.handle_list record_or_hash_or_array
208
+ when String, Symbol
209
+ method, args = builder.handle_string record_or_hash_or_array
210
+ when Class
211
+ method, args = builder.handle_class record_or_hash_or_array
212
+
213
+ when nil
214
+ raise ArgumentError, "Nil location provided. Can't build URI."
186
215
  else
187
- record = extract_record(records)
188
- route = []
216
+ method, args = builder.handle_model record_or_hash_or_array
189
217
  end
190
218
 
191
- if record.is_a?(Symbol) || record.is_a?(String)
192
- route << record
193
- elsif record
194
- if inflection == :singular
195
- route << model_name_from_record_or_class(record).singular_route_key
219
+
220
+ if options.empty?
221
+ recipient.send(method, *args)
222
+ else
223
+ recipient.send(method, *args, options)
224
+ end
225
+ end
226
+
227
+ attr_reader :suffix, :prefix
228
+
229
+ def initialize(key_strategy, prefix, suffix)
230
+ @key_strategy = key_strategy
231
+ @prefix = prefix
232
+ @suffix = suffix
233
+ end
234
+
235
+ def handle_string(record)
236
+ [get_method_for_string(record), []]
237
+ end
238
+
239
+ def handle_string_call(target, str)
240
+ target.send get_method_for_string str
241
+ end
242
+
243
+ def handle_class(klass)
244
+ [get_method_for_class(klass), []]
245
+ end
246
+
247
+ def handle_class_call(target, klass)
248
+ target.send get_method_for_class klass
249
+ end
250
+
251
+ def handle_model(record)
252
+ args = []
253
+
254
+ model = record.to_model
255
+ name = if record.persisted?
256
+ args << model
257
+ model.model_name.singular_route_key
258
+ else
259
+ @key_strategy.call model.model_name
260
+ end
261
+
262
+ named_route = prefix + "#{name}_#{suffix}"
263
+
264
+ [named_route, args]
265
+ end
266
+
267
+ def handle_model_call(target, model)
268
+ method, args = handle_model model
269
+ target.send(method, *args)
270
+ end
271
+
272
+ def handle_list(list)
273
+ record_list = list.dup
274
+ record = record_list.pop
275
+
276
+ args = []
277
+
278
+ route = record_list.map { |parent|
279
+ case parent
280
+ when Symbol, String
281
+ parent.to_s
282
+ when Class
283
+ args << parent
284
+ parent.model_name.singular_route_key
196
285
  else
197
- route << model_name_from_record_or_class(record).route_key
286
+ args << parent.to_model
287
+ parent.to_model.model_name.singular_route_key
198
288
  end
289
+ }
290
+
291
+ route <<
292
+ case record
293
+ when Symbol, String
294
+ record.to_s
295
+ when Class
296
+ @key_strategy.call record.model_name
199
297
  else
200
- raise ArgumentError, "Nil location provided. Can't build URI."
298
+ if record.persisted?
299
+ args << record.to_model
300
+ record.to_model.model_name.singular_route_key
301
+ else
302
+ @key_strategy.call record.to_model.model_name
303
+ end
201
304
  end
202
305
 
203
- route << routing_type(options)
306
+ route << suffix
204
307
 
205
- action_prefix(options) + route.join("_")
308
+ named_route = prefix + route.join("_")
309
+ [named_route, args]
206
310
  end
207
311
 
208
- def extract_record(record_or_hash_or_array)
209
- case record_or_hash_or_array
210
- when Array; record_or_hash_or_array.last
211
- when Hash; record_or_hash_or_array[:id]
212
- else record_or_hash_or_array
213
- end
312
+ private
313
+
314
+ def get_method_for_class(klass)
315
+ name = @key_strategy.call klass.model_name
316
+ prefix + "#{name}_#{suffix}"
317
+ end
318
+
319
+ def get_method_for_string(str)
320
+ prefix + "#{str}_#{suffix}"
214
321
  end
322
+
323
+ [nil, 'new', 'edit'].each do |action|
324
+ CACHE['url'][action] = build action, 'url'
325
+ CACHE['path'][action] = build action, 'path'
326
+ end
327
+ end
215
328
  end
216
329
  end
217
330
  end
218
-
@@ -3,10 +3,11 @@ require 'active_support/core_ext/uri'
3
3
  require 'active_support/core_ext/array/extract_options'
4
4
  require 'rack/utils'
5
5
  require 'action_controller/metal/exceptions'
6
+ require 'action_dispatch/routing/endpoint'
6
7
 
7
8
  module ActionDispatch
8
9
  module Routing
9
- class Redirect # :nodoc:
10
+ class Redirect < Endpoint # :nodoc:
10
11
  attr_reader :status, :block
11
12
 
12
13
  def initialize(status, block)
@@ -14,18 +15,15 @@ module ActionDispatch
14
15
  @block = block
15
16
  end
16
17
 
17
- def call(env)
18
- req = Request.new(env)
18
+ def redirect?; true; end
19
19
 
20
- # If any of the path parameters has an invalid encoding then
21
- # raise since it's likely to trigger errors further on.
22
- req.symbolized_path_parameters.each do |key, value|
23
- unless value.valid_encoding?
24
- raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
25
- end
26
- end
20
+ def call(env)
21
+ serve Request.new env
22
+ end
27
23
 
28
- uri = URI.parse(path(req.symbolized_path_parameters, req))
24
+ def serve(req)
25
+ req.check_path_parameters!
26
+ uri = URI.parse(path(req.path_parameters, req))
29
27
 
30
28
  unless uri.host
31
29
  if relative_path?(uri.path)
@@ -39,7 +37,7 @@ module ActionDispatch
39
37
  uri.host ||= req.host
40
38
  uri.port ||= req.port unless req.standard_port?
41
39
 
42
- body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
40
+ body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>)
43
41
 
44
42
  headers = {
45
43
  'Location' => uri.to_s,
@@ -1,5 +1,6 @@
1
1
  require 'action_dispatch/journey'
2
2
  require 'forwardable'
3
+ require 'thread_safe'
3
4
  require 'active_support/concern'
4
5
  require 'active_support/core_ext/object/to_query'
5
6
  require 'active_support/core_ext/hash/slice'
@@ -7,6 +8,7 @@ require 'active_support/core_ext/module/remove_method'
7
8
  require 'active_support/core_ext/array/extract_options'
8
9
  require 'action_controller/metal/exceptions'
9
10
  require 'action_dispatch/http/request'
11
+ require 'action_dispatch/routing/endpoint'
10
12
 
11
13
  module ActionDispatch
12
14
  module Routing
@@ -17,26 +19,17 @@ module ActionDispatch
17
19
  # alias inspect to to_s.
18
20
  alias inspect to_s
19
21
 
20
- PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
21
-
22
- class Dispatcher #:nodoc:
23
- def initialize(options={})
24
- @defaults = options[:defaults]
25
- @glob_param = options.delete(:glob)
22
+ class Dispatcher < Routing::Endpoint #:nodoc:
23
+ def initialize(defaults)
24
+ @defaults = defaults
25
+ @controller_class_names = ThreadSafe::Cache.new
26
26
  end
27
27
 
28
- def call(env)
29
- params = env[PARAMETERS_KEY]
30
-
31
- # If any of the path parameters has an invalid encoding then
32
- # raise since it's likely to trigger errors further on.
33
- params.each do |key, value|
34
- next unless value.respond_to?(:valid_encoding?)
28
+ def dispatcher?; true; end
35
29
 
36
- unless value.valid_encoding?
37
- raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
38
- end
39
- end
30
+ def serve(req)
31
+ req.check_path_parameters!
32
+ params = req.path_parameters
40
33
 
41
34
  prepare_params!(params)
42
35
 
@@ -45,13 +38,12 @@ module ActionDispatch
45
38
  return [404, {'X-Cascade' => 'pass'}, []]
46
39
  end
47
40
 
48
- dispatch(controller, params[:action], env)
41
+ dispatch(controller, params[:action], req.env)
49
42
  end
50
43
 
51
44
  def prepare_params!(params)
52
45
  normalize_controller!(params)
53
46
  merge_default_action!(params)
54
- split_glob_param!(params) if @glob_param
55
47
  end
56
48
 
57
49
  # If this is a default_controller (i.e. a controller specified by the user)
@@ -72,7 +64,7 @@ module ActionDispatch
72
64
  private
73
65
 
74
66
  def controller_reference(controller_param)
75
- const_name = "#{controller_param.camelize}Controller"
67
+ const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
76
68
  ActiveSupport::Dependencies.constantize(const_name)
77
69
  end
78
70
 
@@ -87,10 +79,6 @@ module ActionDispatch
87
79
  def merge_default_action!(params)
88
80
  params[:action] ||= 'index'
89
81
  end
90
-
91
- def split_glob_param!(params)
92
- params[@glob_param] = params[@glob_param].split('/').map { |v| URI.parser.unescape(v) }
93
- end
94
82
  end
95
83
 
96
84
  # A NamedRouteCollection instance is a collection of named routes, and also
@@ -98,36 +86,69 @@ module ActionDispatch
98
86
  # named routes.
99
87
  class NamedRouteCollection #:nodoc:
100
88
  include Enumerable
101
- attr_reader :routes, :helpers, :module
89
+ attr_reader :routes, :url_helpers_module
102
90
 
103
91
  def initialize
104
92
  @routes = {}
105
- @helpers = []
106
- @module = Module.new
93
+ @path_helpers = Set.new
94
+ @url_helpers = Set.new
95
+ @url_helpers_module = Module.new
96
+ @path_helpers_module = Module.new
97
+ end
98
+
99
+ def route_defined?(name)
100
+ key = name.to_sym
101
+ @path_helpers.include?(key) || @url_helpers.include?(key)
102
+ end
103
+
104
+ def helpers
105
+ ActiveSupport::Deprecation.warn("`named_routes.helpers` is deprecated, please use `route_defined?(route_name)` to see if a named route was defined.")
106
+ @path_helpers + @url_helpers
107
107
  end
108
108
 
109
109
  def helper_names
110
- @helpers.map(&:to_s)
110
+ @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
111
111
  end
112
112
 
113
113
  def clear!
114
- @helpers.each do |helper|
115
- @module.remove_possible_method helper
114
+ @path_helpers.each do |helper|
115
+ @path_helpers_module.send :undef_method, helper
116
+ end
117
+
118
+ @url_helpers.each do |helper|
119
+ @url_helpers_module.send :undef_method, helper
116
120
  end
117
121
 
118
122
  @routes.clear
119
- @helpers.clear
123
+ @path_helpers.clear
124
+ @url_helpers.clear
120
125
  end
121
126
 
122
127
  def add(name, route)
123
- routes[name.to_sym] = route
124
- define_named_route_methods(name, route)
128
+ key = name.to_sym
129
+ path_name = :"#{name}_path"
130
+ url_name = :"#{name}_url"
131
+
132
+ if routes.key? key
133
+ @path_helpers_module.send :undef_method, path_name
134
+ @url_helpers_module.send :undef_method, url_name
135
+ end
136
+ routes[key] = route
137
+ define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
138
+ define_url_helper @url_helpers_module, route, url_name, route.defaults, name, FULL
139
+
140
+ @path_helpers << path_name
141
+ @url_helpers << url_name
125
142
  end
126
143
 
127
144
  def get(name)
128
145
  routes[name.to_sym]
129
146
  end
130
147
 
148
+ def key?(name)
149
+ routes.key? name.to_sym
150
+ end
151
+
131
152
  alias []= add
132
153
  alias [] get
133
154
  alias clear clear!
@@ -145,36 +166,54 @@ module ActionDispatch
145
166
  routes.length
146
167
  end
147
168
 
169
+ def path_helpers_module(warn = false)
170
+ if warn
171
+ mod = @path_helpers_module
172
+ helpers = @path_helpers
173
+ Module.new do
174
+ include mod
175
+
176
+ helpers.each do |meth|
177
+ define_method(meth) do |*args, &block|
178
+ ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead")
179
+ super(*args, &block)
180
+ end
181
+ end
182
+ end
183
+ else
184
+ @path_helpers_module
185
+ end
186
+ end
187
+
148
188
  class UrlHelper # :nodoc:
149
- def self.create(route, options)
189
+ def self.create(route, options, route_name, url_strategy)
150
190
  if optimize_helper?(route)
151
- OptimizedUrlHelper.new(route, options)
191
+ OptimizedUrlHelper.new(route, options, route_name, url_strategy)
152
192
  else
153
- new route, options
193
+ new route, options, route_name, url_strategy
154
194
  end
155
195
  end
156
196
 
157
197
  def self.optimize_helper?(route)
158
- !route.glob? && route.requirements.except(:controller, :action).empty?
198
+ !route.glob? && route.path.requirements.empty?
159
199
  end
160
200
 
201
+ attr_reader :url_strategy, :route_name
202
+
161
203
  class OptimizedUrlHelper < UrlHelper # :nodoc:
162
204
  attr_reader :arg_size
163
205
 
164
- def initialize(route, options)
206
+ def initialize(route, options, route_name, url_strategy)
165
207
  super
166
- @klass = Journey::Router::Utils
167
208
  @required_parts = @route.required_parts
168
209
  @arg_size = @required_parts.size
169
- @optimized_path = @route.optimized_path
170
210
  end
171
211
 
172
- def call(t, args)
173
- if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
174
- options = @options.dup
175
- options.merge!(t.url_options) if t.respond_to?(:url_options)
212
+ def call(t, args, inner_options)
213
+ if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
214
+ options = t.url_options.merge @options
176
215
  options[:path] = optimized_helper(args)
177
- ActionDispatch::Http::URL.url_for(options)
216
+ url_strategy.call options
178
217
  else
179
218
  super
180
219
  end
@@ -183,18 +222,14 @@ module ActionDispatch
183
222
  private
184
223
 
185
224
  def optimized_helper(args)
186
- params = Hash[parameterize_args(args)]
225
+ params = parameterize_args(args)
187
226
  missing_keys = missing_keys(params)
188
227
 
189
228
  unless missing_keys.empty?
190
229
  raise_generation_error(params, missing_keys)
191
230
  end
192
231
 
193
- @optimized_path.map{ |segment| replace_segment(params, segment) }.join
194
- end
195
-
196
- def replace_segment(params, segment)
197
- Symbol === segment ? @klass.escape_segment(params[segment]) : segment
232
+ @route.format params
198
233
  end
199
234
 
200
235
  def optimize_routes_generation?(t)
@@ -202,7 +237,9 @@ module ActionDispatch
202
237
  end
203
238
 
204
239
  def parameterize_args(args)
205
- @required_parts.zip(args.map(&:to_param))
240
+ params = {}
241
+ @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v }
242
+ params
206
243
  end
207
244
 
208
245
  def missing_keys(args)
@@ -218,41 +255,36 @@ module ActionDispatch
218
255
  end
219
256
  end
220
257
 
221
- def initialize(route, options)
258
+ def initialize(route, options, route_name, url_strategy)
222
259
  @options = options
223
260
  @segment_keys = route.segment_keys.uniq
224
261
  @route = route
262
+ @url_strategy = url_strategy
263
+ @route_name = route_name
225
264
  end
226
265
 
227
- def call(t, args)
228
- t.url_for(handle_positional_args(t, args, @options, @segment_keys))
266
+ def call(t, args, inner_options)
267
+ controller_options = t.url_options
268
+ options = controller_options.merge @options
269
+ hash = handle_positional_args(controller_options,
270
+ inner_options || {},
271
+ args,
272
+ options,
273
+ @segment_keys)
274
+
275
+ t._routes.url_for(hash, route_name, url_strategy)
229
276
  end
230
277
 
231
- def handle_positional_args(t, args, options, keys)
232
- inner_options = args.extract_options!
233
- result = options.dup
278
+ def handle_positional_args(controller_options, inner_options, args, result, path_params)
234
279
 
235
280
  if args.size > 0
236
- # take format into account
237
- if keys.include?(:format)
238
- keys_size = keys.size - 1
239
- else
240
- keys_size = keys.size
241
- end
242
-
243
- if args.size < keys_size
244
- keys -= t.url_options.keys if t.respond_to?(:url_options)
245
- keys -= options.keys
246
- end
247
- keys -= inner_options.keys
248
-
249
- keys.each do |key|
250
- value = inner_options.fetch(key) { args.shift }
251
-
252
- unless key == :format && value.nil?
253
- result[key] = value
254
- end
281
+ if args.size < path_params.size - 1 # take format into account
282
+ path_params -= controller_options.keys
283
+ path_params -= result.keys
255
284
  end
285
+ path_params.each { |param|
286
+ result[param] = inner_options[param] || args.shift
287
+ }
256
288
  end
257
289
 
258
290
  result.merge!(inner_options)
@@ -273,27 +305,25 @@ module ActionDispatch
273
305
  #
274
306
  # foo_url(bar, baz, bang, sort_by: 'baz')
275
307
  #
276
- def define_url_helper(route, name, options)
277
- helper = UrlHelper.create(route, options.dup)
278
-
279
- @module.remove_possible_method name
280
- @module.module_eval do
308
+ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
309
+ helper = UrlHelper.create(route, opts, route_key, url_strategy)
310
+ mod.module_eval do
281
311
  define_method(name) do |*args|
282
- helper.call self, args
312
+ options = nil
313
+ options = args.pop if args.last.is_a? Hash
314
+ helper.call self, args, options
283
315
  end
284
316
  end
285
-
286
- helpers << name
287
- end
288
-
289
- def define_named_route_methods(name, route)
290
- define_url_helper route, :"#{name}_path",
291
- route.defaults.merge(:use_route => name, :only_path => true)
292
- define_url_helper route, :"#{name}_url",
293
- route.defaults.merge(:use_route => name, :only_path => false)
294
317
  end
295
318
  end
296
319
 
320
+ # :stopdoc:
321
+ # strategy for building urls to send to the client
322
+ PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
323
+ FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) }
324
+ UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
325
+ # :startdoc:
326
+
297
327
  attr_accessor :formatter, :set, :named_routes, :default_scope, :router
298
328
  attr_accessor :disable_clear_and_finalize, :resources_path_names
299
329
  attr_accessor :default_url_options, :request_class
@@ -306,7 +336,7 @@ module ActionDispatch
306
336
 
307
337
  def initialize(request_class = ActionDispatch::Request)
308
338
  self.named_routes = NamedRouteCollection.new
309
- self.resources_path_names = self.class.default_resources_path_names.dup
339
+ self.resources_path_names = self.class.default_resources_path_names
310
340
  self.default_url_options = {}
311
341
  self.request_class = request_class
312
342
 
@@ -316,9 +346,7 @@ module ActionDispatch
316
346
  @finalized = false
317
347
 
318
348
  @set = Journey::Routes.new
319
- @router = Journey::Router.new(@set, {
320
- :parameters_key => PARAMETERS_KEY,
321
- :request_class => request_class})
349
+ @router = Journey::Router.new @set
322
350
  @formatter = Journey::Formatter.new @set
323
351
  end
324
352
 
@@ -349,6 +377,7 @@ module ActionDispatch
349
377
  mapper.instance_exec(&block)
350
378
  end
351
379
  end
380
+ private :eval_block
352
381
 
353
382
  def finalize!
354
383
  return if @finalized
@@ -364,6 +393,10 @@ module ActionDispatch
364
393
  @prepend.each { |blk| eval_block(blk) }
365
394
  end
366
395
 
396
+ def dispatcher(defaults)
397
+ Routing::RouteSet::Dispatcher.new(defaults)
398
+ end
399
+
367
400
  module MountedHelpers #:nodoc:
368
401
  extend ActiveSupport::Concern
369
402
  include UrlFor
@@ -394,40 +427,51 @@ module ActionDispatch
394
427
  RUBY
395
428
  end
396
429
 
397
- def url_helpers
398
- @url_helpers ||= begin
399
- routes = self
430
+ def url_helpers(include_path_helpers = true)
431
+ routes = self
400
432
 
401
- Module.new do
402
- extend ActiveSupport::Concern
403
- include UrlFor
433
+ Module.new do
434
+ extend ActiveSupport::Concern
435
+ include UrlFor
436
+
437
+ # Define url_for in the singleton level so one can do:
438
+ # Rails.application.routes.url_helpers.url_for(args)
439
+ @_routes = routes
440
+ class << self
441
+ delegate :url_for, :optimize_routes_generation?, to: '@_routes'
442
+ attr_reader :_routes
443
+ def url_options; {}; end
444
+ end
404
445
 
405
- # Define url_for in the singleton level so one can do:
406
- # Rails.application.routes.url_helpers.url_for(args)
407
- @_routes = routes
408
- class << self
409
- delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
410
- end
446
+ url_helpers = routes.named_routes.url_helpers_module
411
447
 
412
- # Make named_routes available in the module singleton
413
- # as well, so one can do:
414
- # Rails.application.routes.url_helpers.posts_path
415
- extend routes.named_routes.module
448
+ # Make named_routes available in the module singleton
449
+ # as well, so one can do:
450
+ # Rails.application.routes.url_helpers.posts_path
451
+ extend url_helpers
416
452
 
417
- # Any class that includes this module will get all
418
- # named routes...
419
- include routes.named_routes.module
453
+ # Any class that includes this module will get all
454
+ # named routes...
455
+ include url_helpers
420
456
 
421
- # plus a singleton class method called _routes ...
422
- included do
423
- singleton_class.send(:redefine_method, :_routes) { routes }
424
- end
457
+ if include_path_helpers
458
+ path_helpers = routes.named_routes.path_helpers_module
459
+ else
460
+ path_helpers = routes.named_routes.path_helpers_module(true)
461
+ end
425
462
 
426
- # And an instance method _routes. Note that
427
- # UrlFor (included in this module) add extra
428
- # conveniences for working with @_routes.
429
- define_method(:_routes) { @_routes || routes }
463
+ include path_helpers
464
+ extend path_helpers
465
+
466
+ # plus a singleton class method called _routes ...
467
+ included do
468
+ singleton_class.send(:redefine_method, :_routes) { routes }
430
469
  end
470
+
471
+ # And an instance method _routes. Note that
472
+ # UrlFor (included in this module) add extra
473
+ # conveniences for working with @_routes.
474
+ define_method(:_routes) { @_routes || routes }
431
475
  end
432
476
  end
433
477
 
@@ -446,7 +490,9 @@ module ActionDispatch
446
490
  "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
447
491
  end
448
492
 
449
- path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
493
+ path = conditions.delete :path_info
494
+ ast = conditions.delete :parsed_path_info
495
+ path = build_path(path, ast, requirements, anchor)
450
496
  conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
451
497
 
452
498
  route = @set.add_route(app, path, conditions, defaults, name)
@@ -454,8 +500,9 @@ module ActionDispatch
454
500
  route
455
501
  end
456
502
 
457
- def build_path(path, requirements, separators, anchor)
503
+ def build_path(path, ast, requirements, anchor)
458
504
  strexp = Journey::Router::Strexp.new(
505
+ ast,
459
506
  path,
460
507
  requirements,
461
508
  SEPARATORS,
@@ -516,8 +563,8 @@ module ActionDispatch
516
563
 
517
564
  attr_reader :options, :recall, :set, :named_route
518
565
 
519
- def initialize(options, recall, set)
520
- @named_route = options.delete(:use_route)
566
+ def initialize(named_route, options, recall, set)
567
+ @named_route = named_route
521
568
  @options = options.dup
522
569
  @recall = recall.dup
523
570
  @set = set
@@ -608,7 +655,7 @@ module ActionDispatch
608
655
  # Generates a path from routes, returns [path, params].
609
656
  # If no route is generated the formatter will raise ActionController::UrlGenerationError
610
657
  def generate
611
- @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
658
+ @set.formatter.generate(named_route, options, recall, PARAMETERIZE)
612
659
  end
613
660
 
614
661
  def different_controller?
@@ -633,61 +680,74 @@ module ActionDispatch
633
680
  end
634
681
 
635
682
  def generate_extras(options, recall={})
636
- path, params = generate(options, recall)
683
+ route_key = options.delete :use_route
684
+ path, params = generate(route_key, options, recall)
637
685
  return path, params.keys
638
686
  end
639
687
 
640
- def generate(options, recall = {})
641
- Generator.new(options, recall, self).generate
688
+ def generate(route_key, options, recall = {})
689
+ Generator.new(route_key, options, recall, self).generate
642
690
  end
691
+ private :generate
643
692
 
644
693
  RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
645
694
  :trailing_slash, :anchor, :params, :only_path, :script_name,
646
695
  :original_script_name]
647
696
 
648
- def mounted?
649
- false
697
+ def optimize_routes_generation?
698
+ default_url_options.empty?
650
699
  end
651
700
 
652
- def optimize_routes_generation?
653
- !mounted? && default_url_options.empty?
701
+ def find_script_name(options)
702
+ options.delete(:script_name) { '' }
654
703
  end
655
704
 
656
- def _generate_prefix(options = {})
657
- nil
705
+ def path_for(options, route_name = nil) # :nodoc:
706
+ url_for(options, route_name, PATH)
658
707
  end
659
708
 
660
- # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
661
- def url_for(options)
662
- options = default_url_options.merge(options || {})
709
+ # The +options+ argument must be a hash whose keys are *symbols*.
710
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN)
711
+ options = default_url_options.merge options
663
712
 
664
- user, password = extract_authentication(options)
665
- recall = options.delete(:_recall)
713
+ user = password = nil
714
+
715
+ if options[:user] && options[:password]
716
+ user = options.delete :user
717
+ password = options.delete :password
718
+ end
666
719
 
667
- original_script_name = options.delete(:original_script_name).presence
668
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
720
+ recall = options.delete(:_recall) { {} }
669
721
 
670
- if script_name && original_script_name
722
+ original_script_name = options.delete(:original_script_name)
723
+ script_name = find_script_name options
724
+
725
+ if original_script_name
671
726
  script_name = original_script_name + script_name
672
727
  end
673
728
 
674
- path_options = options.except(*RESERVED_OPTIONS)
675
- path_options = yield(path_options) if block_given?
729
+ path_options = options.dup
730
+ RESERVED_OPTIONS.each { |ro| path_options.delete ro }
731
+
732
+ path, params = generate(route_name, path_options, recall)
733
+
734
+ if options.key? :params
735
+ params.merge! options[:params]
736
+ end
676
737
 
677
- path, params = generate(path_options, recall || {})
678
- params.merge!(options[:params] || {})
738
+ options[:path] = path
739
+ options[:script_name] = script_name
740
+ options[:params] = params
741
+ options[:user] = user
742
+ options[:password] = password
679
743
 
680
- ActionDispatch::Http::URL.url_for(options.merge!({
681
- :path => path,
682
- :script_name => script_name,
683
- :params => params,
684
- :user => user,
685
- :password => password
686
- }))
744
+ url_strategy.call options
687
745
  end
688
746
 
689
747
  def call(env)
690
- @router.call(env)
748
+ req = request_class.new(env)
749
+ req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
750
+ @router.serve(req)
691
751
  end
692
752
 
693
753
  def recognize_path(path, environment = {})
@@ -701,8 +761,8 @@ module ActionDispatch
701
761
  raise ActionController::RoutingError, e.message
702
762
  end
703
763
 
704
- req = @request_class.new(env)
705
- @router.recognize(req) do |route, _matches, params|
764
+ req = request_class.new(env)
765
+ @router.recognize(req) do |route, params|
706
766
  params.merge!(extras)
707
767
  params.each do |key, value|
708
768
  if value.is_a?(String)
@@ -710,14 +770,12 @@ module ActionDispatch
710
770
  params[key] = URI.parser.unescape(value)
711
771
  end
712
772
  end
713
- old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
714
- env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
715
- dispatcher = route.app
716
- while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
717
- dispatcher = dispatcher.app
718
- end
773
+ old_params = req.path_parameters
774
+ req.path_parameters = old_params.merge params
775
+ app = route.app
776
+ if app.matches?(req) && app.dispatcher?
777
+ dispatcher = app.app
719
778
 
720
- if dispatcher.is_a?(Dispatcher)
721
779
  if dispatcher.controller(params, false)
722
780
  dispatcher.prepare_params!(params)
723
781
  return params
@@ -729,17 +787,6 @@ module ActionDispatch
729
787
 
730
788
  raise ActionController::RoutingError, "No route matches #{path.inspect}"
731
789
  end
732
-
733
- private
734
-
735
- def extract_authentication(options)
736
- if options[:user] && options[:password]
737
- [options.delete(:user), options.delete(:password)]
738
- else
739
- nil
740
- end
741
- end
742
-
743
790
  end
744
791
  end
745
792
  end