actionpack 4.1.7 → 4.2.11

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 (112) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +404 -451
  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/rendering.rb +7 -1
  9. data/lib/abstract_controller/url_for.rb +1 -1
  10. data/lib/action_controller/base.rb +3 -2
  11. data/lib/action_controller/caching/fragments.rb +7 -1
  12. data/lib/action_controller/caching.rb +1 -1
  13. data/lib/action_controller/log_subscriber.rb +26 -26
  14. data/lib/action_controller/metal/conditional_get.rb +37 -12
  15. data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
  16. data/lib/action_controller/metal/exceptions.rb +1 -1
  17. data/lib/action_controller/metal/force_ssl.rb +1 -1
  18. data/lib/action_controller/metal/head.rb +7 -3
  19. data/lib/action_controller/metal/http_authentication.rb +20 -10
  20. data/lib/action_controller/metal/instrumentation.rb +8 -5
  21. data/lib/action_controller/metal/live.rb +57 -6
  22. data/lib/action_controller/metal/mime_responds.rb +25 -246
  23. data/lib/action_controller/metal/params_wrapper.rb +5 -5
  24. data/lib/action_controller/metal/rack_delegation.rb +1 -1
  25. data/lib/action_controller/metal/redirecting.rb +14 -8
  26. data/lib/action_controller/metal/renderers.rb +29 -11
  27. data/lib/action_controller/metal/rendering.rb +2 -6
  28. data/lib/action_controller/metal/request_forgery_protection.rb +78 -7
  29. data/lib/action_controller/metal/streaming.rb +1 -1
  30. data/lib/action_controller/metal/strong_parameters.rb +129 -14
  31. data/lib/action_controller/metal/url_for.rb +11 -12
  32. data/lib/action_controller/metal.rb +12 -11
  33. data/lib/action_controller/model_naming.rb +1 -1
  34. data/lib/action_controller/railtie.rb +4 -0
  35. data/lib/action_controller/test_case.rb +119 -75
  36. data/lib/action_controller.rb +1 -1
  37. data/lib/action_dispatch/http/cache.rb +5 -4
  38. data/lib/action_dispatch/http/filter_parameters.rb +2 -2
  39. data/lib/action_dispatch/http/headers.rb +43 -9
  40. data/lib/action_dispatch/http/mime_negotiation.rb +10 -3
  41. data/lib/action_dispatch/http/mime_type.rb +18 -4
  42. data/lib/action_dispatch/http/parameter_filter.rb +1 -1
  43. data/lib/action_dispatch/http/parameters.rb +11 -26
  44. data/lib/action_dispatch/http/request.rb +37 -11
  45. data/lib/action_dispatch/http/response.rb +74 -23
  46. data/lib/action_dispatch/http/upload.rb +9 -8
  47. data/lib/action_dispatch/http/url.rb +89 -70
  48. data/lib/action_dispatch/journey/formatter.rb +34 -18
  49. data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
  50. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
  51. data/lib/action_dispatch/journey/gtg/transition_table.rb +20 -28
  52. data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
  53. data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
  54. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
  55. data/lib/action_dispatch/journey/nodes/node.rb +4 -0
  56. data/lib/action_dispatch/journey/parser.rb +52 -60
  57. data/lib/action_dispatch/journey/parser.y +11 -10
  58. data/lib/action_dispatch/journey/path/pattern.rb +16 -19
  59. data/lib/action_dispatch/journey/route.rb +4 -19
  60. data/lib/action_dispatch/journey/router/strexp.rb +9 -6
  61. data/lib/action_dispatch/journey/router/utils.rb +1 -1
  62. data/lib/action_dispatch/journey/router.rb +53 -77
  63. data/lib/action_dispatch/journey/routes.rb +4 -0
  64. data/lib/action_dispatch/journey/scanner.rb +5 -5
  65. data/lib/action_dispatch/journey/visitors.rb +81 -92
  66. data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
  67. data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
  68. data/lib/action_dispatch/middleware/callbacks.rb +1 -1
  69. data/lib/action_dispatch/middleware/cookies.rb +34 -34
  70. data/lib/action_dispatch/middleware/debug_exceptions.rb +15 -4
  71. data/lib/action_dispatch/middleware/exception_wrapper.rb +50 -18
  72. data/lib/action_dispatch/middleware/flash.rb +13 -7
  73. data/lib/action_dispatch/middleware/params_parser.rb +1 -1
  74. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
  75. data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
  76. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  77. data/lib/action_dispatch/middleware/session/cookie_store.rb +1 -1
  78. data/lib/action_dispatch/middleware/show_exceptions.rb +1 -0
  79. data/lib/action_dispatch/middleware/ssl.rb +1 -1
  80. data/lib/action_dispatch/middleware/static.rb +75 -39
  81. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
  82. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +37 -9
  83. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +2 -8
  84. data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +0 -0
  85. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  86. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +4 -0
  88. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +2 -0
  89. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -24
  90. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +0 -1
  91. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
  92. data/lib/action_dispatch/railtie.rb +2 -0
  93. data/lib/action_dispatch/routing/endpoint.rb +10 -0
  94. data/lib/action_dispatch/routing/inspector.rb +5 -12
  95. data/lib/action_dispatch/routing/mapper.rb +414 -283
  96. data/lib/action_dispatch/routing/polymorphic_routes.rb +191 -79
  97. data/lib/action_dispatch/routing/redirection.rb +10 -12
  98. data/lib/action_dispatch/routing/route_set.rb +300 -173
  99. data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
  100. data/lib/action_dispatch/routing/url_for.rb +17 -5
  101. data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
  102. data/lib/action_dispatch/testing/assertions/response.rb +2 -7
  103. data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
  104. data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
  105. data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
  106. data/lib/action_dispatch/testing/assertions.rb +11 -7
  107. data/lib/action_dispatch/testing/integration.rb +28 -20
  108. data/lib/action_dispatch/testing/test_request.rb +1 -1
  109. data/lib/action_dispatch/testing/test_response.rb +1 -5
  110. data/lib/action_pack/gem_version.rb +3 -3
  111. metadata +55 -13
  112. 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,
@@ -1,44 +1,37 @@
1
1
  require 'action_dispatch/journey'
2
2
  require 'forwardable'
3
- require 'thread_safe'
4
3
  require 'active_support/concern'
5
4
  require 'active_support/core_ext/object/to_query'
6
5
  require 'active_support/core_ext/hash/slice'
7
6
  require 'active_support/core_ext/module/remove_method'
8
7
  require 'active_support/core_ext/array/extract_options'
8
+ require 'active_support/core_ext/string/filters'
9
9
  require 'action_controller/metal/exceptions'
10
10
  require 'action_dispatch/http/request'
11
+ require 'action_dispatch/routing/endpoint'
11
12
 
12
13
  module ActionDispatch
13
14
  module Routing
14
- class RouteSet #:nodoc:
15
+ # :stopdoc:
16
+ class RouteSet
15
17
  # Since the router holds references to many parts of the system
16
18
  # like engines, controllers and the application itself, inspecting
17
19
  # the route set can actually be really slow, therefore we default
18
20
  # alias inspect to to_s.
19
21
  alias inspect to_s
20
22
 
21
- PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
23
+ mattr_accessor :relative_url_root
22
24
 
23
- class Dispatcher #:nodoc:
24
- def initialize(options={})
25
- @defaults = options[:defaults]
26
- @glob_param = options.delete(:glob)
27
- @controller_class_names = ThreadSafe::Cache.new
25
+ class Dispatcher < Routing::Endpoint
26
+ def initialize(defaults)
27
+ @defaults = defaults
28
28
  end
29
29
 
30
- def call(env)
31
- params = env[PARAMETERS_KEY]
30
+ def dispatcher?; true; end
32
31
 
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?)
37
-
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)
@@ -74,7 +66,7 @@ module ActionDispatch
74
66
  private
75
67
 
76
68
  def controller_reference(controller_param)
77
- const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller"
69
+ const_name = "#{controller_param.camelize}Controller"
78
70
  ActiveSupport::Dependencies.constantize(const_name)
79
71
  end
80
72
 
@@ -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)
@@ -212,7 +252,7 @@ module ActionDispatch
212
252
  end
213
253
 
214
254
  def raise_generation_error(args, missing_keys)
215
- constraints = Hash[@route.requirements.merge(args).sort]
255
+ constraints = Hash[@route.requirements.merge(args).sort_by{|k,v| k.to_s}]
216
256
  message = "No route matches #{constraints.inspect}"
217
257
  message << " missing required keys: #{missing_keys.sort.inspect}"
218
258
 
@@ -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
241
290
  end
242
- keys -= inner_options.keys
243
- result.merge!(Hash[keys.zip(args)])
291
+
292
+ if args.size < path_params_size
293
+ path_params -= controller_options.keys
294
+ path_params -= result.keys
295
+ end
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
@@ -369,9 +469,11 @@ module ActionDispatch
369
469
  return if MountedHelpers.method_defined?(name)
370
470
 
371
471
  routes = self
472
+ helpers = routes.url_helpers
473
+
372
474
  MountedHelpers.class_eval do
373
475
  define_method "_#{name}" do
374
- RoutesProxy.new(routes, _routes_context)
476
+ RoutesProxy.new(routes, _routes_context, helpers)
375
477
  end
376
478
  end
377
479
 
@@ -382,40 +484,57 @@ module ActionDispatch
382
484
  RUBY
383
485
  end
384
486
 
385
- def url_helpers
386
- @url_helpers ||= begin
387
- routes = self
487
+ def url_helpers(supports_path = true)
488
+ routes = self
388
489
 
389
- Module.new do
390
- extend ActiveSupport::Concern
391
- include UrlFor
490
+ Module.new do
491
+ extend ActiveSupport::Concern
492
+ include UrlFor
493
+
494
+ # Define url_for in the singleton level so one can do:
495
+ # Rails.application.routes.url_helpers.url_for(args)
496
+ @_routes = routes
497
+ class << self
498
+ delegate :url_for, :optimize_routes_generation?, to: '@_routes'
499
+ attr_reader :_routes
500
+ def url_options; {}; end
501
+ end
392
502
 
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
503
+ url_helpers = routes.named_routes.url_helpers_module
399
504
 
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
505
+ # Make named_routes available in the module singleton
506
+ # as well, so one can do:
507
+ # Rails.application.routes.url_helpers.posts_path
508
+ extend url_helpers
404
509
 
405
- # Any class that includes this module will get all
406
- # named routes...
407
- include routes.named_routes.module
510
+ # Any class that includes this module will get all
511
+ # named routes...
512
+ include url_helpers
408
513
 
409
- # plus a singleton class method called _routes ...
410
- included do
411
- singleton_class.send(:redefine_method, :_routes) { routes }
412
- end
514
+ if supports_path
515
+ path_helpers = routes.named_routes.path_helpers_module
516
+ else
517
+ path_helpers = routes.named_routes.path_helpers_module(true)
518
+ end
519
+
520
+ include path_helpers
521
+ extend path_helpers
413
522
 
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 }
523
+ # plus a singleton class method called _routes ...
524
+ included do
525
+ singleton_class.send(:redefine_method, :_routes) { routes }
418
526
  end
527
+
528
+ # And an instance method _routes. Note that
529
+ # UrlFor (included in this module) add extra
530
+ # conveniences for working with @_routes.
531
+ define_method(:_routes) { @_routes || routes }
532
+
533
+ define_method(:_generate_paths_by_default) do
534
+ supports_path
535
+ end
536
+
537
+ private :_generate_paths_by_default
419
538
  end
420
539
  end
421
540
 
@@ -434,7 +553,9 @@ module ActionDispatch
434
553
  "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created"
435
554
  end
436
555
 
437
- path = build_path(conditions.delete(:path_info), requirements, SEPARATORS, anchor)
556
+ path = conditions.delete :path_info
557
+ ast = conditions.delete :parsed_path_info
558
+ path = build_path(path, ast, requirements, anchor)
438
559
  conditions = build_conditions(conditions, path.names.map { |x| x.to_sym })
439
560
 
440
561
  route = @set.add_route(app, path, conditions, defaults, name)
@@ -442,8 +563,9 @@ module ActionDispatch
442
563
  route
443
564
  end
444
565
 
445
- def build_path(path, requirements, separators, anchor)
566
+ def build_path(path, ast, requirements, anchor)
446
567
  strexp = Journey::Router::Strexp.new(
568
+ ast,
447
569
  path,
448
570
  requirements,
449
571
  SEPARATORS,
@@ -491,7 +613,7 @@ module ActionDispatch
491
613
  end
492
614
  private :build_conditions
493
615
 
494
- class Generator #:nodoc:
616
+ class Generator
495
617
  PARAMETERIZE = lambda do |name, value|
496
618
  if name == :controller
497
619
  value
@@ -504,8 +626,8 @@ module ActionDispatch
504
626
 
505
627
  attr_reader :options, :recall, :set, :named_route
506
628
 
507
- def initialize(options, recall, set)
508
- @named_route = options.delete(:use_route)
629
+ def initialize(named_route, options, recall, set)
630
+ @named_route = named_route
509
631
  @options = options.dup
510
632
  @recall = recall.dup
511
633
  @set = set
@@ -596,7 +718,7 @@ module ActionDispatch
596
718
  # Generates a path from routes, returns [path, params].
597
719
  # If no route is generated the formatter will raise ActionController::UrlGenerationError
598
720
  def generate
599
- @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE)
721
+ @set.formatter.generate(named_route, options, recall, PARAMETERIZE)
600
722
  end
601
723
 
602
724
  def different_controller?
@@ -621,61 +743,78 @@ module ActionDispatch
621
743
  end
622
744
 
623
745
  def generate_extras(options, recall={})
624
- path, params = generate(options, recall)
746
+ route_key = options.delete :use_route
747
+ path, params = generate(route_key, options, recall)
625
748
  return path, params.keys
626
749
  end
627
750
 
628
- def generate(options, recall = {})
629
- Generator.new(options, recall, self).generate
751
+ def generate(route_key, options, recall = {})
752
+ Generator.new(route_key, options, recall, self).generate
630
753
  end
754
+ private :generate
631
755
 
632
756
  RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
633
757
  :trailing_slash, :anchor, :params, :only_path, :script_name,
634
- :original_script_name]
758
+ :original_script_name, :relative_url_root]
635
759
 
636
- def mounted?
637
- false
760
+ def optimize_routes_generation?
761
+ default_url_options.empty?
638
762
  end
639
763
 
640
- def optimize_routes_generation?
641
- !mounted? && default_url_options.empty?
764
+ def find_script_name(options)
765
+ options.delete(:script_name) || find_relative_url_root(options) || ''
642
766
  end
643
767
 
644
- def _generate_prefix(options = {})
645
- nil
768
+ def find_relative_url_root(options)
769
+ options.delete(:relative_url_root) || relative_url_root
770
+ end
771
+
772
+ def path_for(options, route_name = nil)
773
+ url_for(options, route_name, PATH)
646
774
  end
647
775
 
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 || {})
776
+ # The +options+ argument must be a hash whose keys are *symbols*.
777
+ def url_for(options, route_name = nil, url_strategy = UNKNOWN)
778
+ options = default_url_options.merge options
651
779
 
652
- user, password = extract_authentication(options)
653
- recall = options.delete(:_recall)
780
+ user = password = nil
654
781
 
655
- original_script_name = options.delete(:original_script_name).presence
656
- script_name = options.delete(:script_name).presence || _generate_prefix(options)
782
+ if options[:user] && options[:password]
783
+ user = options.delete :user
784
+ password = options.delete :password
785
+ end
786
+
787
+ recall = options.delete(:_recall) { {} }
788
+
789
+ original_script_name = options.delete(:original_script_name)
790
+ script_name = find_script_name options
657
791
 
658
- if script_name && original_script_name
792
+ if original_script_name
659
793
  script_name = original_script_name + script_name
660
794
  end
661
795
 
662
- path_options = options.except(*RESERVED_OPTIONS)
663
- path_options = yield(path_options) if block_given?
796
+ path_options = options.dup
797
+ RESERVED_OPTIONS.each { |ro| path_options.delete ro }
664
798
 
665
- path, params = generate(path_options, recall || {})
666
- params.merge!(options[:params] || {})
799
+ path, params = generate(route_name, path_options, recall)
800
+
801
+ if options.key? :params
802
+ params.merge! options[:params]
803
+ end
667
804
 
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
- }))
805
+ options[:path] = path
806
+ options[:script_name] = script_name
807
+ options[:params] = params
808
+ options[:user] = user
809
+ options[:password] = password
810
+
811
+ url_strategy.call options
675
812
  end
676
813
 
677
814
  def call(env)
678
- @router.call(env)
815
+ req = request_class.new(env)
816
+ req.path_info = Journey::Router::Utils.normalize_path(req.path_info)
817
+ @router.serve(req)
679
818
  end
680
819
 
681
820
  def recognize_path(path, environment = {})
@@ -689,8 +828,8 @@ module ActionDispatch
689
828
  raise ActionController::RoutingError, e.message
690
829
  end
691
830
 
692
- req = @request_class.new(env)
693
- @router.recognize(req) do |route, _matches, params|
831
+ req = request_class.new(env)
832
+ @router.recognize(req) do |route, params|
694
833
  params.merge!(extras)
695
834
  params.each do |key, value|
696
835
  if value.is_a?(String)
@@ -698,14 +837,12 @@ module ActionDispatch
698
837
  params[key] = URI.parser.unescape(value)
699
838
  end
700
839
  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
840
+ old_params = req.path_parameters
841
+ req.path_parameters = old_params.merge params
842
+ app = route.app
843
+ if app.matches?(req) && app.dispatcher?
844
+ dispatcher = app.app
707
845
 
708
- if dispatcher.is_a?(Dispatcher)
709
846
  if dispatcher.controller(params, false)
710
847
  dispatcher.prepare_params!(params)
711
848
  return params
@@ -717,17 +854,7 @@ module ActionDispatch
717
854
 
718
855
  raise ActionController::RoutingError, "No route matches #{path.inspect}"
719
856
  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
857
  end
858
+ # :startdoc:
732
859
  end
733
860
  end