actionpack 4.1.7 → 4.2.1

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +311 -527
  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 +11 -4
  7. data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
  8. data/lib/abstract_controller/url_for.rb +1 -1
  9. data/lib/action_controller/base.rb +2 -1
  10. data/lib/action_controller/caching/fragments.rb +7 -1
  11. data/lib/action_controller/caching.rb +1 -1
  12. data/lib/action_controller/log_subscriber.rb +26 -26
  13. data/lib/action_controller/metal/conditional_get.rb +37 -12
  14. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  15. data/lib/action_controller/metal/exceptions.rb +1 -1
  16. data/lib/action_controller/metal/force_ssl.rb +1 -1
  17. data/lib/action_controller/metal/head.rb +7 -3
  18. data/lib/action_controller/metal/http_authentication.rb +14 -9
  19. data/lib/action_controller/metal/instrumentation.rb +8 -5
  20. data/lib/action_controller/metal/live.rb +57 -6
  21. data/lib/action_controller/metal/mime_responds.rb +23 -246
  22. data/lib/action_controller/metal/params_wrapper.rb +2 -2
  23. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  24. data/lib/action_controller/metal/redirecting.rb +14 -8
  25. data/lib/action_controller/metal/renderers.rb +30 -10
  26. data/lib/action_controller/metal/rendering.rb +2 -6
  27. data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
  28. data/lib/action_controller/metal/streaming.rb +1 -1
  29. data/lib/action_controller/metal/strong_parameters.rb +125 -12
  30. data/lib/action_controller/metal/url_for.rb +11 -12
  31. data/lib/action_controller/metal.rb +12 -11
  32. data/lib/action_controller/model_naming.rb +1 -1
  33. data/lib/action_controller/railtie.rb +4 -0
  34. data/lib/action_controller/test_case.rb +112 -75
  35. data/lib/action_controller.rb +1 -1
  36. data/lib/action_dispatch/http/cache.rb +5 -4
  37. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  38. data/lib/action_dispatch/http/headers.rb +43 -9
  39. data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
  40. data/lib/action_dispatch/http/mime_type.rb +2 -2
  41. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  42. data/lib/action_dispatch/http/parameters.rb +11 -26
  43. data/lib/action_dispatch/http/request.rb +37 -11
  44. data/lib/action_dispatch/http/response.rb +70 -18
  45. data/lib/action_dispatch/http/upload.rb +3 -8
  46. data/lib/action_dispatch/http/url.rb +88 -69
  47. data/lib/action_dispatch/journey/formatter.rb +33 -17
  48. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  49. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  50. data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
  51. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  52. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  53. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  54. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  55. data/lib/action_dispatch/journey/parser.rb +52 -60
  56. data/lib/action_dispatch/journey/parser.y +11 -10
  57. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  58. data/lib/action_dispatch/journey/route.rb +3 -18
  59. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  60. data/lib/action_dispatch/journey/router.rb +53 -77
  61. data/lib/action_dispatch/journey/scanner.rb +5 -5
  62. data/lib/action_dispatch/journey/visitors.rb +81 -92
  63. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  64. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  65. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  66. data/lib/action_dispatch/middleware/cookies.rb +29 -29
  67. data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
  68. data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
  69. data/lib/action_dispatch/middleware/flash.rb +13 -7
  70. data/lib/action_dispatch/middleware/params_parser.rb +1 -1
  71. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  72. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  73. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  74. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  75. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  76. data/lib/action_dispatch/middleware/static.rb +66 -37
  77. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  78. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
  79. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  80. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  81. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  82. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  83. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
  84. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  86. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  87. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  88. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  89. data/lib/action_dispatch/routing/inspector.rb +5 -12
  90. data/lib/action_dispatch/routing/mapper.rb +410 -281
  91. data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
  92. data/lib/action_dispatch/routing/redirection.rb +10 -12
  93. data/lib/action_dispatch/routing/route_set.rb +297 -168
  94. data/lib/action_dispatch/routing/url_for.rb +15 -4
  95. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  96. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  97. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  98. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  99. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  100. data/lib/action_dispatch/testing/assertions.rb +11 -7
  101. data/lib/action_dispatch/testing/integration.rb +24 -19
  102. data/lib/action_dispatch/testing/test_request.rb +1 -1
  103. data/lib/action_dispatch/testing/test_response.rb +7 -0
  104. data/lib/action_pack/gem_version.rb +3 -3
  105. metadata +55 -13
  106. 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
110
- end
111
-
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 ]
118
-
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
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
130
108
  end
131
109
 
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)
110
+ opts = options.dup
111
+ action = opts.delete :action
112
+ type = opts.delete(:routing_type) || :url
134
113
 
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)
114
+ HelperMethodBuilder.polymorphic_method self,
115
+ record_or_hash_or_array,
116
+ action,
117
+ type,
118
+ opts
143
119
  end
144
120
 
145
121
  # Returns the path component of a URL for the given record. It uses
146
122
  # <tt>polymorphic_url</tt> with <tt>routing_type: :path</tt>.
147
123
  def polymorphic_path(record_or_hash_or_array, options = {})
148
- polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
124
+ if Hash === record_or_hash_or_array
125
+ options = record_or_hash_or_array.merge(options)
126
+ record = options.delete :id
127
+ return polymorphic_path record, options
128
+ end
129
+
130
+ opts = options.dup
131
+ action = opts.delete :action
132
+ type = :path
133
+
134
+ HelperMethodBuilder.polymorphic_method self,
135
+ record_or_hash_or_array,
136
+ action,
137
+ type,
138
+ opts
149
139
  end
150
140
 
141
+
151
142
  %w(edit new).each do |action|
152
143
  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
144
+ def #{action}_polymorphic_url(record_or_hash, options = {})
145
+ polymorphic_url_for_action("#{action}", record_or_hash, options)
146
+ end
147
+
148
+ def #{action}_polymorphic_path(record_or_hash, options = {})
149
+ polymorphic_path_for_action("#{action}", record_or_hash, options)
150
+ end
164
151
  EOT
165
152
  end
166
153
 
167
154
  private
168
- def action_prefix(options)
169
- options[:action] ? "#{options[:action]}_" : ''
155
+
156
+ def polymorphic_url_for_action(action, record_or_hash, options)
157
+ polymorphic_url(record_or_hash, options.merge(:action => action))
158
+ end
159
+
160
+ def polymorphic_path_for_action(action, record_or_hash, options)
161
+ polymorphic_path(record_or_hash, options.merge(:action => action))
162
+ end
163
+
164
+ class HelperMethodBuilder # :nodoc:
165
+ CACHE = { 'path' => {}, 'url' => {} }
166
+
167
+ def self.get(action, type)
168
+ type = type.to_s
169
+ CACHE[type].fetch(action) { build action, type }
170
+ end
171
+
172
+ def self.url; CACHE['url'.freeze][nil]; end
173
+ def self.path; CACHE['path'.freeze][nil]; end
174
+
175
+ def self.build(action, type)
176
+ prefix = action ? "#{action}_" : ""
177
+ suffix = type
178
+ if action.to_s == 'new'
179
+ HelperMethodBuilder.singular prefix, suffix
180
+ else
181
+ HelperMethodBuilder.plural prefix, suffix
182
+ end
183
+ end
184
+
185
+ def self.singular(prefix, suffix)
186
+ new(->(name) { name.singular_route_key }, prefix, suffix)
170
187
  end
171
188
 
172
- def routing_type(options)
173
- options[:routing_type] || :url
189
+ def self.plural(prefix, suffix)
190
+ new(->(name) { name.route_key }, prefix, suffix)
174
191
  end
175
192
 
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
193
+ def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options)
194
+ builder = get action, type
195
+
196
+ case record_or_hash_or_array
197
+ when Array
198
+ record_or_hash_or_array = record_or_hash_or_array.compact
199
+ if record_or_hash_or_array.empty?
200
+ raise ArgumentError, "Nil location provided. Can't build URI."
185
201
  end
202
+ if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy)
203
+ recipient = record_or_hash_or_array.shift
204
+ end
205
+
206
+ method, args = builder.handle_list record_or_hash_or_array
207
+ when String, Symbol
208
+ method, args = builder.handle_string record_or_hash_or_array
209
+ when Class
210
+ method, args = builder.handle_class record_or_hash_or_array
211
+
212
+ when nil
213
+ raise ArgumentError, "Nil location provided. Can't build URI."
214
+ else
215
+ method, args = builder.handle_model record_or_hash_or_array
216
+ end
217
+
218
+
219
+ if options.empty?
220
+ recipient.send(method, *args)
186
221
  else
187
- record = extract_record(records)
188
- route = []
222
+ recipient.send(method, *args, options)
189
223
  end
224
+ end
225
+
226
+ attr_reader :suffix, :prefix
227
+
228
+ def initialize(key_strategy, prefix, suffix)
229
+ @key_strategy = key_strategy
230
+ @prefix = prefix
231
+ @suffix = suffix
232
+ end
233
+
234
+ def handle_string(record)
235
+ [get_method_for_string(record), []]
236
+ end
237
+
238
+ def handle_string_call(target, str)
239
+ target.send get_method_for_string str
240
+ end
241
+
242
+ def handle_class(klass)
243
+ [get_method_for_class(klass), []]
244
+ end
245
+
246
+ def handle_class_call(target, klass)
247
+ target.send get_method_for_class klass
248
+ end
249
+
250
+ def handle_model(record)
251
+ args = []
252
+
253
+ model = record.to_model
254
+ name = if model.persisted?
255
+ args << model
256
+ model.model_name.singular_route_key
257
+ else
258
+ @key_strategy.call model.model_name
259
+ end
260
+
261
+ named_route = prefix + "#{name}_#{suffix}"
262
+
263
+ [named_route, args]
264
+ end
265
+
266
+ def handle_model_call(target, model)
267
+ method, args = handle_model model
268
+ target.send(method, *args)
269
+ end
270
+
271
+ def handle_list(list)
272
+ record_list = list.dup
273
+ record = record_list.pop
274
+
275
+ args = []
190
276
 
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
277
+ route = record_list.map { |parent|
278
+ case parent
279
+ when Symbol, String
280
+ parent.to_s
281
+ when Class
282
+ args << parent
283
+ parent.model_name.singular_route_key
196
284
  else
197
- route << model_name_from_record_or_class(record).route_key
285
+ args << parent.to_model
286
+ parent.to_model.model_name.singular_route_key
198
287
  end
288
+ }
289
+
290
+ route <<
291
+ case record
292
+ when Symbol, String
293
+ record.to_s
294
+ when Class
295
+ @key_strategy.call record.model_name
199
296
  else
200
- raise ArgumentError, "Nil location provided. Can't build URI."
297
+ model = record.to_model
298
+ if model.persisted?
299
+ args << model
300
+ model.model_name.singular_route_key
301
+ else
302
+ @key_strategy.call 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,
@@ -6,39 +6,32 @@ require 'active_support/core_ext/object/to_query'
6
6
  require 'active_support/core_ext/hash/slice'
7
7
  require 'active_support/core_ext/module/remove_method'
8
8
  require 'active_support/core_ext/array/extract_options'
9
+ require 'active_support/core_ext/string/filters'
9
10
  require 'action_controller/metal/exceptions'
10
11
  require 'action_dispatch/http/request'
12
+ require 'action_dispatch/routing/endpoint'
11
13
 
12
14
  module ActionDispatch
13
15
  module Routing
14
- class RouteSet #:nodoc:
16
+ # :stopdoc:
17
+ class RouteSet
15
18
  # Since the router holds references to many parts of the system
16
19
  # like engines, controllers and the application itself, inspecting
17
20
  # the route set can actually be really slow, therefore we default
18
21
  # alias inspect to to_s.
19
22
  alias inspect to_s
20
23
 
21
- PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
22
-
23
- class Dispatcher #:nodoc:
24
- def initialize(options={})
25
- @defaults = options[:defaults]
26
- @glob_param = options.delete(:glob)
24
+ class Dispatcher < Routing::Endpoint
25
+ def initialize(defaults)
26
+ @defaults = defaults
27
27
  @controller_class_names = ThreadSafe::Cache.new
28
28
  end
29
29
 
30
- def call(env)
31
- params = env[PARAMETERS_KEY]
32
-
33
- # If any of the path parameters has an invalid encoding then
34
- # raise since it's likely to trigger errors further on.
35
- params.each do |key, value|
36
- next unless value.respond_to?(:valid_encoding?)
30
+ def dispatcher?; true; end
37
31
 
38
- unless value.valid_encoding?
39
- raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}"
40
- end
41
- end
32
+ def serve(req)
33
+ req.check_path_parameters!
34
+ params = req.path_parameters
42
35
 
43
36
  prepare_params!(params)
44
37
 
@@ -47,13 +40,12 @@ module ActionDispatch
47
40
  return [404, {'X-Cascade' => 'pass'}, []]
48
41
  end
49
42
 
50
- dispatch(controller, params[:action], env)
43
+ dispatch(controller, params[:action], req.env)
51
44
  end
52
45
 
53
46
  def prepare_params!(params)
54
47
  normalize_controller!(params)
55
48
  merge_default_action!(params)
56
- split_glob_param!(params) if @glob_param
57
49
  end
58
50
 
59
51
  # If this is a default_controller (i.e. a controller specified by the user)
@@ -89,47 +81,79 @@ module ActionDispatch
89
81
  def merge_default_action!(params)
90
82
  params[:action] ||= 'index'
91
83
  end
92
-
93
- def split_glob_param!(params)
94
- params[@glob_param] = params[@glob_param].split('/').map { |v| URI.parser.unescape(v) }
95
- end
96
84
  end
97
85
 
98
86
  # A NamedRouteCollection instance is a collection of named routes, and also
99
87
  # maintains an anonymous module that can be used to install helpers for the
100
88
  # named routes.
101
- class NamedRouteCollection #:nodoc:
89
+ class NamedRouteCollection
102
90
  include Enumerable
103
- attr_reader :routes, :helpers, :module
91
+ attr_reader :routes, :url_helpers_module
104
92
 
105
93
  def initialize
106
94
  @routes = {}
107
- @helpers = []
108
- @module = Module.new
95
+ @path_helpers = Set.new
96
+ @url_helpers = Set.new
97
+ @url_helpers_module = Module.new
98
+ @path_helpers_module = Module.new
99
+ end
100
+
101
+ def route_defined?(name)
102
+ key = name.to_sym
103
+ @path_helpers.include?(key) || @url_helpers.include?(key)
104
+ end
105
+
106
+ def helpers
107
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
108
+ `named_routes.helpers` is deprecated, please use `route_defined?(route_name)`
109
+ to see if a named route was defined.
110
+ MSG
111
+ @path_helpers + @url_helpers
109
112
  end
110
113
 
111
114
  def helper_names
112
- @helpers.map(&:to_s)
115
+ @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s)
113
116
  end
114
117
 
115
118
  def clear!
116
- @helpers.each do |helper|
117
- @module.remove_possible_method helper
119
+ @path_helpers.each do |helper|
120
+ @path_helpers_module.send :undef_method, helper
121
+ end
122
+
123
+ @url_helpers.each do |helper|
124
+ @url_helpers_module.send :undef_method, helper
118
125
  end
119
126
 
120
127
  @routes.clear
121
- @helpers.clear
128
+ @path_helpers.clear
129
+ @url_helpers.clear
122
130
  end
123
131
 
124
132
  def add(name, route)
125
- routes[name.to_sym] = route
126
- define_named_route_methods(name, route)
133
+ key = name.to_sym
134
+ path_name = :"#{name}_path"
135
+ url_name = :"#{name}_url"
136
+
137
+ if routes.key? key
138
+ @path_helpers_module.send :undef_method, path_name
139
+ @url_helpers_module.send :undef_method, url_name
140
+ end
141
+ routes[key] = route
142
+ define_url_helper @path_helpers_module, route, path_name, route.defaults, name, LEGACY
143
+ define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN
144
+
145
+ @path_helpers << path_name
146
+ @url_helpers << url_name
127
147
  end
128
148
 
129
149
  def get(name)
130
150
  routes[name.to_sym]
131
151
  end
132
152
 
153
+ def key?(name)
154
+ routes.key? name.to_sym
155
+ end
156
+
133
157
  alias []= add
134
158
  alias [] get
135
159
  alias clear clear!
@@ -147,36 +171,54 @@ module ActionDispatch
147
171
  routes.length
148
172
  end
149
173
 
150
- class UrlHelper # :nodoc:
151
- def self.create(route, options)
174
+ def path_helpers_module(warn = false)
175
+ if warn
176
+ mod = @path_helpers_module
177
+ helpers = @path_helpers
178
+ Module.new do
179
+ include mod
180
+
181
+ helpers.each do |meth|
182
+ define_method(meth) do |*args, &block|
183
+ ActiveSupport::Deprecation.warn("The method `#{meth}` cannot be used here as a full URL is required. Use `#{meth.to_s.sub(/_path$/, '_url')}` instead")
184
+ super(*args, &block)
185
+ end
186
+ end
187
+ end
188
+ else
189
+ @path_helpers_module
190
+ end
191
+ end
192
+
193
+ class UrlHelper
194
+ def self.create(route, options, route_name, url_strategy)
152
195
  if optimize_helper?(route)
153
- OptimizedUrlHelper.new(route, options)
196
+ OptimizedUrlHelper.new(route, options, route_name, url_strategy)
154
197
  else
155
- new route, options
198
+ new route, options, route_name, url_strategy
156
199
  end
157
200
  end
158
201
 
159
202
  def self.optimize_helper?(route)
160
- !route.glob? && route.requirements.except(:controller, :action).empty?
203
+ !route.glob? && route.path.requirements.empty?
161
204
  end
162
205
 
163
- class OptimizedUrlHelper < UrlHelper # :nodoc:
206
+ attr_reader :url_strategy, :route_name
207
+
208
+ class OptimizedUrlHelper < UrlHelper
164
209
  attr_reader :arg_size
165
210
 
166
- def initialize(route, options)
211
+ def initialize(route, options, route_name, url_strategy)
167
212
  super
168
- @klass = Journey::Router::Utils
169
213
  @required_parts = @route.required_parts
170
214
  @arg_size = @required_parts.size
171
- @optimized_path = @route.optimized_path
172
215
  end
173
216
 
174
- def call(t, args)
175
- if args.size == arg_size && !args.last.is_a?(Hash) && optimize_routes_generation?(t)
176
- options = @options.dup
177
- options.merge!(t.url_options) if t.respond_to?(:url_options)
217
+ def call(t, args, inner_options)
218
+ if args.size == arg_size && !inner_options && optimize_routes_generation?(t)
219
+ options = t.url_options.merge @options
178
220
  options[:path] = optimized_helper(args)
179
- ActionDispatch::Http::URL.url_for(options)
221
+ url_strategy.call options
180
222
  else
181
223
  super
182
224
  end
@@ -185,18 +227,14 @@ module ActionDispatch
185
227
  private
186
228
 
187
229
  def optimized_helper(args)
188
- params = Hash[parameterize_args(args)]
230
+ params = parameterize_args(args)
189
231
  missing_keys = missing_keys(params)
190
232
 
191
233
  unless missing_keys.empty?
192
234
  raise_generation_error(params, missing_keys)
193
235
  end
194
236
 
195
- @optimized_path.map{ |segment| replace_segment(params, segment) }.join
196
- end
197
-
198
- def replace_segment(params, segment)
199
- Symbol === segment ? @klass.escape_segment(params[segment]) : segment
237
+ @route.format params
200
238
  end
201
239
 
202
240
  def optimize_routes_generation?(t)
@@ -204,7 +242,9 @@ module ActionDispatch
204
242
  end
205
243
 
206
244
  def parameterize_args(args)
207
- @required_parts.zip(args.map(&:to_param))
245
+ params = {}
246
+ @required_parts.zip(args.map(&:to_param)) { |k,v| params[k] = v }
247
+ params
208
248
  end
209
249
 
210
250
  def missing_keys(args)
@@ -220,31 +260,66 @@ module ActionDispatch
220
260
  end
221
261
  end
222
262
 
223
- def initialize(route, options)
263
+ def initialize(route, options, route_name, url_strategy)
224
264
  @options = options
225
265
  @segment_keys = route.segment_keys.uniq
226
266
  @route = route
267
+ @url_strategy = url_strategy
268
+ @route_name = route_name
227
269
  end
228
270
 
229
- def call(t, args)
230
- t.url_for(handle_positional_args(t, args, @options, @segment_keys))
231
- end
271
+ def call(t, args, inner_options)
272
+ controller_options = t.url_options
273
+ options = controller_options.merge @options
274
+ hash = handle_positional_args(controller_options,
275
+ deprecate_string_options(inner_options) || {},
276
+ args,
277
+ options,
278
+ @segment_keys)
232
279
 
233
- def handle_positional_args(t, args, options, keys)
234
- inner_options = args.extract_options!
235
- result = options.dup
280
+ t._routes.url_for(hash, route_name, url_strategy)
281
+ end
236
282
 
283
+ def handle_positional_args(controller_options, inner_options, args, result, path_params)
237
284
  if args.size > 0
238
- if args.size < keys.size - 1 # take format into account
239
- keys -= t.url_options.keys if t.respond_to?(:url_options)
240
- keys -= options.keys
285
+ # take format into account
286
+ if path_params.include?(:format)
287
+ path_params_size = path_params.size - 1
288
+ else
289
+ path_params_size = path_params.size
290
+ end
291
+
292
+ if args.size < path_params_size
293
+ path_params -= controller_options.keys
294
+ path_params -= result.keys
241
295
  end
242
- keys -= inner_options.keys
243
- result.merge!(Hash[keys.zip(args)])
296
+ path_params.each { |param|
297
+ value = inner_options.fetch(param) { args.shift }
298
+
299
+ unless param == :format && value.nil?
300
+ result[param] = value
301
+ end
302
+ }
244
303
  end
245
304
 
246
305
  result.merge!(inner_options)
247
306
  end
307
+
308
+ DEPRECATED_STRING_OPTIONS = %w[controller action]
309
+
310
+ def deprecate_string_options(options)
311
+ options ||= {}
312
+ deprecated_string_options = options.keys & DEPRECATED_STRING_OPTIONS
313
+ if deprecated_string_options.any?
314
+ msg = "Calling URL helpers with string keys #{deprecated_string_options.join(", ")} is deprecated. Use symbols instead."
315
+ ActiveSupport::Deprecation.warn(msg)
316
+ deprecated_string_options.each do |option|
317
+ value = options.delete(option)
318
+ options[option.to_sym] = value
319
+ end
320
+ end
321
+ options
322
+ end
248
323
  end
249
324
 
250
325
  private
@@ -261,26 +336,48 @@ module ActionDispatch
261
336
  #
262
337
  # foo_url(bar, baz, bang, sort_by: 'baz')
263
338
  #
264
- def define_url_helper(route, name, options)
265
- helper = UrlHelper.create(route, options.dup)
266
-
267
- @module.remove_possible_method name
268
- @module.module_eval do
339
+ def define_url_helper(mod, route, name, opts, route_key, url_strategy)
340
+ helper = UrlHelper.create(route, opts, route_key, url_strategy)
341
+ mod.module_eval do
269
342
  define_method(name) do |*args|
270
- helper.call self, args
343
+ options = nil
344
+ options = args.pop if args.last.is_a? Hash
345
+ helper.call self, args, options
271
346
  end
272
347
  end
273
-
274
- helpers << name
275
348
  end
349
+ end
276
350
 
277
- def define_named_route_methods(name, route)
278
- define_url_helper route, :"#{name}_path",
279
- route.defaults.merge(:use_route => name, :only_path => true)
280
- define_url_helper route, :"#{name}_url",
281
- route.defaults.merge(:use_route => name, :only_path => false)
351
+ # strategy for building urls to send to the client
352
+ PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) }
353
+ FULL = ->(options) { ActionDispatch::Http::URL.full_url_for(options) }
354
+ UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
355
+ LEGACY = ->(options) {
356
+ if options.key?(:only_path)
357
+ if options[:only_path]
358
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
359
+ You are calling a `*_path` helper with the `only_path` option
360
+ explicitly set to `true`. This option will stop working on
361
+ path helpers in Rails 5. Simply remove the `only_path: true`
362
+ argument from your call as it is redundant when applied to a
363
+ path helper.
364
+ MSG
365
+
366
+ PATH.call(options)
367
+ else
368
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
369
+ You are calling a `*_path` helper with the `only_path` option
370
+ explicitly set to `false`. This option will stop working on
371
+ path helpers in Rails 5. Use the corresponding `*_url` helper
372
+ instead.
373
+ MSG
374
+
375
+ FULL.call(options)
376
+ end
377
+ else
378
+ PATH.call(options)
282
379
  end
283
- end
380
+ }
284
381
 
285
382
  attr_accessor :formatter, :set, :named_routes, :default_scope, :router
286
383
  attr_accessor :disable_clear_and_finalize, :resources_path_names
@@ -294,7 +391,7 @@ module ActionDispatch
294
391
 
295
392
  def initialize(request_class = ActionDispatch::Request)
296
393
  self.named_routes = NamedRouteCollection.new
297
- self.resources_path_names = self.class.default_resources_path_names.dup
394
+ self.resources_path_names = self.class.default_resources_path_names
298
395
  self.default_url_options = {}
299
396
  self.request_class = request_class
300
397
 
@@ -304,9 +401,7 @@ module ActionDispatch
304
401
  @finalized = false
305
402
 
306
403
  @set = Journey::Routes.new
307
- @router = Journey::Router.new(@set, {
308
- :parameters_key => PARAMETERS_KEY,
309
- :request_class => request_class})
404
+ @router = Journey::Router.new @set
310
405
  @formatter = Journey::Formatter.new @set
311
406
  end
312
407
 
@@ -337,6 +432,7 @@ module ActionDispatch
337
432
  mapper.instance_exec(&block)
338
433
  end
339
434
  end
435
+ private :eval_block
340
436
 
341
437
  def finalize!
342
438
  return if @finalized
@@ -352,7 +448,11 @@ module ActionDispatch
352
448
  @prepend.each { |blk| eval_block(blk) }
353
449
  end
354
450
 
355
- module MountedHelpers #:nodoc:
451
+ def dispatcher(defaults)
452
+ Routing::RouteSet::Dispatcher.new(defaults)
453
+ end
454
+
455
+ module MountedHelpers
356
456
  extend ActiveSupport::Concern
357
457
  include UrlFor
358
458
  end
@@ -382,40 +482,65 @@ module ActionDispatch
382
482
  RUBY
383
483
  end
384
484
 
385
- def url_helpers
386
- @url_helpers ||= begin
387
- routes = self
485
+ def url_helpers(supports_path = true)
486
+ if supports_path
487
+ @url_helpers_with_paths ||= generate_url_helpers(supports_path)
488
+ else
489
+ @url_helpers_without_paths ||= generate_url_helpers(supports_path)
490
+ end
491
+ end
492
+
493
+ def generate_url_helpers(supports_path)
494
+ routes = self
388
495
 
389
- Module.new do
390
- extend ActiveSupport::Concern
391
- include UrlFor
496
+ Module.new do
497
+ extend ActiveSupport::Concern
498
+ include UrlFor
499
+
500
+ # Define url_for in the singleton level so one can do:
501
+ # Rails.application.routes.url_helpers.url_for(args)
502
+ @_routes = routes
503
+ class << self
504
+ delegate :url_for, :optimize_routes_generation?, to: '@_routes'
505
+ attr_reader :_routes
506
+ def url_options; {}; end
507
+ end
392
508
 
393
- # Define url_for in the singleton level so one can do:
394
- # Rails.application.routes.url_helpers.url_for(args)
395
- @_routes = routes
396
- class << self
397
- delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
398
- end
509
+ url_helpers = routes.named_routes.url_helpers_module
399
510
 
400
- # Make named_routes available in the module singleton
401
- # as well, so one can do:
402
- # Rails.application.routes.url_helpers.posts_path
403
- extend routes.named_routes.module
511
+ # Make named_routes available in the module singleton
512
+ # as well, so one can do:
513
+ # Rails.application.routes.url_helpers.posts_path
514
+ extend url_helpers
404
515
 
405
- # Any class that includes this module will get all
406
- # named routes...
407
- include routes.named_routes.module
516
+ # Any class that includes this module will get all
517
+ # named routes...
518
+ include url_helpers
408
519
 
409
- # plus a singleton class method called _routes ...
410
- included do
411
- singleton_class.send(:redefine_method, :_routes) { routes }
412
- end
520
+ if supports_path
521
+ path_helpers = routes.named_routes.path_helpers_module
522
+ else
523
+ path_helpers = routes.named_routes.path_helpers_module(true)
524
+ end
525
+
526
+ include path_helpers
527
+ extend path_helpers
413
528
 
414
- # And an instance method _routes. Note that
415
- # UrlFor (included in this module) add extra
416
- # conveniences for working with @_routes.
417
- define_method(:_routes) { @_routes || routes }
529
+ # plus a singleton class method called _routes ...
530
+ included do
531
+ singleton_class.send(:redefine_method, :_routes) { routes }
418
532
  end
533
+
534
+ # And an instance method _routes. Note that
535
+ # UrlFor (included in this module) add extra
536
+ # conveniences for working with @_routes.
537
+ define_method(:_routes) { @_routes || routes }
538
+
539
+ define_method(:_generate_paths_by_default) do
540
+ supports_path
541
+ end
542
+
543
+ private :_generate_paths_by_default
419
544
  end
420
545
  end
421
546
 
@@ -434,7 +559,9 @@ module ActionDispatch
434
559
  "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
435
560
  end
436
561
 
437
- path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
562
+ path = conditions.delete :path_info
563
+ ast = conditions.delete :parsed_path_info
564
+ path = build_path(path, ast, requirements, anchor)
438
565
  conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
439
566
 
440
567
  route = @set.add_route(app, path, conditions, defaults, name)
@@ -442,8 +569,9 @@ module ActionDispatch
442
569
  route
443
570
  end
444
571
 
445
- def build_path(path, requirements, separators, anchor)
572
+ def build_path(path, ast, requirements, anchor)
446
573
  strexp = Journey::Router::Strexp.new(
574
+ ast,
447
575
  path,
448
576
  requirements,
449
577
  SEPARATORS,
@@ -491,7 +619,7 @@ module ActionDispatch
491
619
  end
492
620
  private :build_conditions
493
621
 
494
- class Generator #:nodoc:
622
+ class Generator
495
623
  PARAMETERIZE = lambda do |name, value|
496
624
  if name == :controller
497
625
  value
@@ -504,8 +632,8 @@ module ActionDispatch
504
632
 
505
633
  attr_reader :options, :recall, :set, :named_route
506
634
 
507
- def initialize(options, recall, set)
508
- @named_route = options.delete(:use_route)
635
+ def initialize(named_route, options, recall, set)
636
+ @named_route = named_route
509
637
  @options = options.dup
510
638
  @recall = recall.dup
511
639
  @set = set
@@ -596,7 +724,7 @@ module ActionDispatch
596
724
  # Generates a path from routes, returns [path, params].
597
725
  # If no route is generated the formatter will raise ActionController::UrlGenerationError
598
726
  def generate
599
- @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
727
+ @set.formatter.generate(named_route, options, recall, PARAMETERIZE)
600
728
  end
601
729
 
602
730
  def different_controller?
@@ -621,61 +749,74 @@ module ActionDispatch
621
749
  end
622
750
 
623
751
  def generate_extras(options, recall={})
624
- path, params = generate(options, recall)
752
+ route_key = options.delete :use_route
753
+ path, params = generate(route_key, options, recall)
625
754
  return path, params.keys
626
755
  end
627
756
 
628
- def generate(options, recall = {})
629
- Generator.new(options, recall, self).generate
757
+ def generate(route_key, options, recall = {})
758
+ Generator.new(route_key, options, recall, self).generate
630
759
  end
760
+ private :generate
631
761
 
632
762
  RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
633
763
  :trailing_slash, :anchor, :params, :only_path, :script_name,
634
764
  :original_script_name]
635
765
 
636
- def mounted?
637
- false
766
+ def optimize_routes_generation?
767
+ default_url_options.empty?
638
768
  end
639
769
 
640
- def optimize_routes_generation?
641
- !mounted? && default_url_options.empty?
770
+ def find_script_name(options)
771
+ options.delete(:script_name) || ''
642
772
  end
643
773
 
644
- def _generate_prefix(options = {})
645
- nil
774
+ def path_for(options, route_name = nil)
775
+ url_for(options, route_name, PATH)
646
776
  end
647
777
 
648
- # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
649
- def url_for(options)
650
- options = default_url_options.merge(options || {})
778
+ # The +options+ argument must be a hash whose keys are *symbols*.
779
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN)
780
+ options = default_url_options.merge options
651
781
 
652
- user, password = extract_authentication(options)
653
- recall = options.delete(:_recall)
782
+ user = password = nil
654
783
 
655
- original_script_name = options.delete(:original_script_name).presence
656
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
784
+ if options[:user] && options[:password]
785
+ user = options.delete :user
786
+ password = options.delete :password
787
+ end
788
+
789
+ recall = options.delete(:_recall) { {} }
790
+
791
+ original_script_name = options.delete(:original_script_name)
792
+ script_name = find_script_name options
657
793
 
658
- if script_name && original_script_name
794
+ if original_script_name
659
795
  script_name = original_script_name + script_name
660
796
  end
661
797
 
662
- path_options = options.except(*RESERVED_OPTIONS)
663
- path_options = yield(path_options) if block_given?
798
+ path_options = options.dup
799
+ RESERVED_OPTIONS.each { |ro| path_options.delete ro }
664
800
 
665
- path, params = generate(path_options, recall || {})
666
- params.merge!(options[:params] || {})
801
+ path, params = generate(route_name, path_options, recall)
802
+
803
+ if options.key? :params
804
+ params.merge! options[:params]
805
+ end
667
806
 
668
- ActionDispatch::Http::URL.url_for(options.merge!({
669
- :path => path,
670
- :script_name => script_name,
671
- :params => params,
672
- :user => user,
673
- :password => password
674
- }))
807
+ options[:path] = path
808
+ options[:script_name] = script_name
809
+ options[:params] = params
810
+ options[:user] = user
811
+ options[:password] = password
812
+
813
+ url_strategy.call options
675
814
  end
676
815
 
677
816
  def call(env)
678
- @router.call(env)
817
+ req = request_class.new(env)
818
+ req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
819
+ @router.serve(req)
679
820
  end
680
821
 
681
822
  def recognize_path(path, environment = {})
@@ -689,8 +830,8 @@ module ActionDispatch
689
830
  raise ActionController::RoutingError, e.message
690
831
  end
691
832
 
692
- req = @request_class.new(env)
693
- @router.recognize(req) do |route, _matches, params|
833
+ req = request_class.new(env)
834
+ @router.recognize(req) do |route, params|
694
835
  params.merge!(extras)
695
836
  params.each do |key, value|
696
837
  if value.is_a?(String)
@@ -698,14 +839,12 @@ module ActionDispatch
698
839
  params[key] = URI.parser.unescape(value)
699
840
  end
700
841
  end
701
- old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
702
- env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
703
- dispatcher = route.app
704
- while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
705
- dispatcher = dispatcher.app
706
- end
842
+ old_params = req.path_parameters
843
+ req.path_parameters = old_params.merge params
844
+ app = route.app
845
+ if app.matches?(req) && app.dispatcher?
846
+ dispatcher = app.app
707
847
 
708
- if dispatcher.is_a?(Dispatcher)
709
848
  if dispatcher.controller(params, false)
710
849
  dispatcher.prepare_params!(params)
711
850
  return params
@@ -717,17 +856,7 @@ module ActionDispatch
717
856
 
718
857
  raise ActionController::RoutingError, "No route matches #{path.inspect}"
719
858
  end
720
-
721
- private
722
-
723
- def extract_authentication(options)
724
- if options[:user] && options[:password]
725
- [options.delete(:user), options.delete(:password)]
726
- else
727
- nil
728
- end
729
- end
730
-
731
859
  end
860
+ # :startdoc:
732
861
  end
733
862
  end