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
@@ -1,4 +1,6 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'active_support/core_ext/string/filters'
3
+ require 'active_support/deprecation'
2
4
  require 'action_dispatch/http/filter_redirect'
3
5
  require 'monitor'
4
6
 
@@ -97,6 +99,9 @@ module ActionDispatch # :nodoc:
97
99
  x
98
100
  end
99
101
 
102
+ def abort
103
+ end
104
+
100
105
  def close
101
106
  @response.commit!
102
107
  @closed = true
@@ -110,10 +115,11 @@ module ActionDispatch # :nodoc:
110
115
  # The underlying body, as a streamable object.
111
116
  attr_reader :stream
112
117
 
113
- def initialize(status = 200, header = {}, body = [])
118
+ def initialize(status = 200, header = {}, body = [], options = {})
114
119
  super()
115
120
 
116
- header = merge_default_headers(header, self.class.default_headers)
121
+ default_headers = options.fetch(:default_headers, self.class.default_headers)
122
+ header = merge_default_headers(header, default_headers)
117
123
 
118
124
  self.body, self.header, self.status = body, header, status
119
125
 
@@ -207,18 +213,6 @@ module ActionDispatch # :nodoc:
207
213
  end
208
214
  alias_method :status_message, :message
209
215
 
210
- def respond_to?(method, include_private = false)
211
- if method.to_s == 'to_path'
212
- stream.respond_to?(method)
213
- else
214
- super
215
- end
216
- end
217
-
218
- def to_path
219
- stream.to_path
220
- end
221
-
222
216
  # Returns the content of the response as a string. This contains the contents
223
217
  # of any calls to <tt>render</tt>.
224
218
  def body
@@ -271,13 +265,39 @@ module ActionDispatch # :nodoc:
271
265
  stream.close if stream.respond_to?(:close)
272
266
  end
273
267
 
268
+ def abort
269
+ if stream.respond_to?(:abort)
270
+ stream.abort
271
+ elsif stream.respond_to?(:close)
272
+ # `stream.close` should really be reserved for a close from the
273
+ # other direction, but we must fall back to it for
274
+ # compatibility.
275
+ stream.close
276
+ end
277
+ end
278
+
274
279
  # Turns the Response into a Rack-compatible array of the status, headers,
275
- # and body.
280
+ # and body. Allows explict splatting:
281
+ #
282
+ # status, headers, body = *response
276
283
  def to_a
277
284
  rack_response @status, @header.to_hash
278
285
  end
279
286
  alias prepare! to_a
280
- alias to_ary to_a
287
+
288
+ # Be super clear that a response object is not an Array. Defining this
289
+ # would make implicit splatting work, but it also makes adding responses
290
+ # as arrays work, and "flattening" responses, cascading to the rack body!
291
+ # Not sensible behavior.
292
+ def to_ary
293
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
294
+ `ActionDispatch::Response#to_ary` no longer performs implicit conversion
295
+ to an array. Please use `response.to_a` instead, or a splat like `status,
296
+ headers, body = *response`.
297
+ MSG
298
+
299
+ to_a
300
+ end
281
301
 
282
302
  # Returns the response cookies, converted to a Hash of (name => value) pairs
283
303
  #
@@ -296,9 +316,6 @@ module ActionDispatch # :nodoc:
296
316
  cookies
297
317
  end
298
318
 
299
- def _status_code
300
- @status
301
- end
302
319
  private
303
320
 
304
321
  def before_committed
@@ -308,9 +325,7 @@ module ActionDispatch # :nodoc:
308
325
  end
309
326
 
310
327
  def merge_default_headers(original, default)
311
- return original unless default.respond_to?(:merge)
312
-
313
- default.merge(original)
328
+ default.respond_to?(:merge) ? default.merge(original) : original
314
329
  end
315
330
 
316
331
  def build_buffer(response, body)
@@ -337,6 +352,42 @@ module ActionDispatch # :nodoc:
337
352
  !@sending_file && @charset != false
338
353
  end
339
354
 
355
+ class RackBody
356
+ def initialize(response)
357
+ @response = response
358
+ end
359
+
360
+ def each(*args, &block)
361
+ @response.each(*args, &block)
362
+ end
363
+
364
+ def close
365
+ # Rack "close" maps to Response#abort, and *not* Response#close
366
+ # (which is used when the controller's finished writing)
367
+ @response.abort
368
+ end
369
+
370
+ def body
371
+ @response.body
372
+ end
373
+
374
+ def respond_to?(method, include_private = false)
375
+ if method.to_s == 'to_path'
376
+ @response.stream.respond_to?(method)
377
+ else
378
+ super
379
+ end
380
+ end
381
+
382
+ def to_path
383
+ @response.stream.to_path
384
+ end
385
+
386
+ def to_ary
387
+ nil
388
+ end
389
+ end
390
+
340
391
  def rack_response(status, header)
341
392
  assign_default_content_type_and_charset!(header)
342
393
  handle_conditional_get!
@@ -347,7 +398,7 @@ module ActionDispatch # :nodoc:
347
398
  header.delete CONTENT_TYPE
348
399
  [status, header, []]
349
400
  else
350
- [status, header, Rack::BodyProxy.new(self){}]
401
+ [status, header, RackBody.new(self)]
351
402
  end
352
403
  end
353
404
  end
@@ -18,6 +18,7 @@ module ActionDispatch
18
18
  # A +Tempfile+ object with the actual uploaded file. Note that some of
19
19
  # its interface is available directly.
20
20
  attr_accessor :tempfile
21
+ alias :to_io :tempfile
21
22
 
22
23
  # A string with the headers of the multipart request.
23
24
  attr_accessor :headers
@@ -26,7 +27,14 @@ module ActionDispatch
26
27
  @tempfile = hash[:tempfile]
27
28
  raise(ArgumentError, ':tempfile is required') unless @tempfile
28
29
 
29
- @original_filename = encode_filename(hash[:filename])
30
+ @original_filename = hash[:filename]
31
+ if @original_filename
32
+ begin
33
+ @original_filename.encode!(Encoding::UTF_8)
34
+ rescue EncodingError
35
+ @original_filename.force_encoding(Encoding::UTF_8)
36
+ end
37
+ end
30
38
  @content_type = hash[:type]
31
39
  @headers = hash[:head]
32
40
  end
@@ -65,13 +73,6 @@ module ActionDispatch
65
73
  def eof?
66
74
  @tempfile.eof?
67
75
  end
68
-
69
- private
70
-
71
- def encode_filename(filename)
72
- # Encode the filename in the utf8 encoding, unless it is nil
73
- filename.force_encoding(Encoding::UTF_8).encode! if filename
74
- end
75
76
  end
76
77
  end
77
78
  end
@@ -5,51 +5,83 @@ module ActionDispatch
5
5
  module Http
6
6
  module URL
7
7
  IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
8
- HOST_REGEXP = /(^.*:\/\/)?([^:]+)(?::(\d+$))?/
8
+ HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/
9
9
  PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/
10
10
 
11
11
  mattr_accessor :tld_length
12
12
  self.tld_length = 1
13
13
 
14
14
  class << self
15
- def extract_domain(host, tld_length = @@tld_length)
16
- host.split('.').last(1 + tld_length).join('.') if named_host?(host)
15
+ def extract_domain(host, tld_length)
16
+ extract_domain_from(host, tld_length) if named_host?(host)
17
17
  end
18
18
 
19
- def extract_subdomains(host, tld_length = @@tld_length)
19
+ def extract_subdomains(host, tld_length)
20
20
  if named_host?(host)
21
- parts = host.split('.')
22
- parts[0..-(tld_length + 2)]
21
+ extract_subdomains_from(host, tld_length)
23
22
  else
24
23
  []
25
24
  end
26
25
  end
27
26
 
28
- def extract_subdomain(host, tld_length = @@tld_length)
27
+ def extract_subdomain(host, tld_length)
29
28
  extract_subdomains(host, tld_length).join('.')
30
29
  end
31
30
 
32
- def url_for(options = {})
33
- options = options.dup
34
- path = options.delete(:script_name).to_s.chomp("/")
35
- path << options.delete(:path).to_s
31
+ def url_for(options)
32
+ if options[:only_path]
33
+ path_for options
34
+ else
35
+ full_url_for options
36
+ end
37
+ end
36
38
 
37
- add_trailing_slash(path) if options[:trailing_slash]
39
+ def full_url_for(options)
40
+ host = options[:host]
41
+ protocol = options[:protocol]
42
+ port = options[:port]
38
43
 
39
- params = options[:params].is_a?(Hash) ? options[:params] : options.slice(:params)
40
- params.reject! { |_,v| v.to_param.nil? }
44
+ unless host
45
+ raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
46
+ end
41
47
 
42
- result = build_host_url(options)
48
+ build_host_url(host, port, protocol, options, path_for(options))
49
+ end
43
50
 
44
- result << path
51
+ def path_for(options)
52
+ path = options[:script_name].to_s.chomp("/")
53
+ path << options[:path] if options.key?(:path)
45
54
 
46
- result << "?#{params.to_query}" unless params.empty?
47
- result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
48
- result
55
+ add_trailing_slash(path) if options[:trailing_slash]
56
+ add_params(path, options[:params]) if options.key?(:params)
57
+ add_anchor(path, options[:anchor]) if options.key?(:anchor)
58
+
59
+ path
49
60
  end
50
61
 
51
62
  private
52
63
 
64
+ def add_params(path, params)
65
+ params = { params: params } unless params.is_a?(Hash)
66
+ params.reject! { |_,v| v.to_param.nil? }
67
+ path << "?#{params.to_query}" unless params.empty?
68
+ end
69
+
70
+ def add_anchor(path, anchor)
71
+ if anchor
72
+ path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}"
73
+ end
74
+ end
75
+
76
+ def extract_domain_from(host, tld_length)
77
+ host.split('.').last(1 + tld_length).join('.')
78
+ end
79
+
80
+ def extract_subdomains_from(host, tld_length)
81
+ parts = host.split('.')
82
+ parts[0..-(tld_length + 2)]
83
+ end
84
+
53
85
  def add_trailing_slash(path)
54
86
  # includes querysting
55
87
  if path.include?('?')
@@ -58,54 +90,38 @@ module ActionDispatch
58
90
  elsif !path.include?(".")
59
91
  path.sub!(/[^\/]\z|\A\z/, '\&/')
60
92
  end
61
-
62
- path
63
93
  end
64
94
 
65
- def build_host_url(options)
66
- if options[:host].blank? && options[:only_path].blank?
67
- raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
95
+ def build_host_url(host, port, protocol, options, path)
96
+ if match = host.match(HOST_REGEXP)
97
+ protocol ||= match[1] unless protocol == false
98
+ host = match[2]
99
+ port = match[3] unless options.key? :port
68
100
  end
69
101
 
70
- result = ""
102
+ protocol = normalize_protocol protocol
103
+ host = normalize_host(host, options)
71
104
 
72
- unless options[:only_path]
73
- if match = options[:host].match(HOST_REGEXP)
74
- options[:protocol] ||= match[1] unless options[:protocol] == false
75
- options[:host] = match[2]
76
- options[:port] = match[3] unless options.key?(:port)
77
- end
105
+ result = protocol.dup
78
106
 
79
- options[:protocol] = normalize_protocol(options)
80
- options[:host] = normalize_host(options)
81
- options[:port] = normalize_port(options)
82
-
83
- result << options[:protocol]
84
- result << rewrite_authentication(options)
85
- result << options[:host]
86
- result << ":#{options[:port]}" if options[:port]
107
+ if options[:user] && options[:password]
108
+ result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
87
109
  end
88
- result
89
- end
90
110
 
91
- def named_host?(host)
92
- host && IP_HOST_REGEXP !~ host
93
- end
111
+ result << host
112
+ normalize_port(port, protocol) { |normalized_port|
113
+ result << ":#{normalized_port}"
114
+ }
94
115
 
95
- def same_host?(options)
96
- (options[:subdomain] == true || !options.key?(:subdomain)) && options[:domain].nil?
116
+ result.concat path
97
117
  end
98
118
 
99
- def rewrite_authentication(options)
100
- if options[:user] && options[:password]
101
- "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
102
- else
103
- ""
104
- end
119
+ def named_host?(host)
120
+ IP_HOST_REGEXP !~ host
105
121
  end
106
122
 
107
- def normalize_protocol(options)
108
- case options[:protocol]
123
+ def normalize_protocol(protocol)
124
+ case protocol
109
125
  when nil
110
126
  "http://"
111
127
  when false, "//"
@@ -113,36 +129,39 @@ module ActionDispatch
113
129
  when PROTOCOL_REGEXP
114
130
  "#{$1}://"
115
131
  else
116
- raise ArgumentError, "Invalid :protocol option: #{options[:protocol].inspect}"
132
+ raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}"
117
133
  end
118
134
  end
119
135
 
120
- def normalize_host(options)
121
- return options[:host] if !named_host?(options[:host]) || same_host?(options)
136
+ def normalize_host(_host, options)
137
+ return _host unless named_host?(_host)
122
138
 
123
139
  tld_length = options[:tld_length] || @@tld_length
140
+ subdomain = options.fetch :subdomain, true
141
+ domain = options[:domain]
124
142
 
125
143
  host = ""
126
- if options[:subdomain] == true || !options.key?(:subdomain)
127
- host << extract_subdomain(options[:host], tld_length).to_param
128
- elsif options[:subdomain].present?
129
- host << options[:subdomain].to_param
144
+ if subdomain == true
145
+ return _host if domain.nil?
146
+
147
+ host << extract_subdomains_from(_host, tld_length).join('.')
148
+ elsif subdomain
149
+ host << subdomain.to_param
130
150
  end
131
151
  host << "." unless host.empty?
132
- host << (options[:domain] || extract_domain(options[:host], tld_length))
152
+ host << (domain || extract_domain_from(_host, tld_length))
133
153
  host
134
154
  end
135
155
 
136
- def normalize_port(options)
137
- return nil if options[:port].nil? || options[:port] == false
156
+ def normalize_port(port, protocol)
157
+ return unless port
138
158
 
139
- case options[:protocol]
140
- when "//"
141
- options[:port]
159
+ case protocol
160
+ when "//" then yield port
142
161
  when "https://"
143
- options[:port].to_i == 443 ? nil : options[:port]
162
+ yield port unless port.to_i == 443
144
163
  else
145
- options[:port].to_i == 80 ? nil : options[:port]
164
+ yield port unless port.to_i == 80
146
165
  end
147
166
  end
148
167
  end
@@ -165,7 +184,7 @@ module ActionDispatch
165
184
 
166
185
  # Returns the \host for this request, such as "example.com".
167
186
  def raw_host_with_port
168
- if forwarded = env["HTTP_X_FORWARDED_HOST"]
187
+ if forwarded = env["HTTP_X_FORWARDED_HOST"].presence
169
188
  forwarded.split(/,\s?/).last
170
189
  else
171
190
  env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
@@ -1,4 +1,5 @@
1
1
  require 'action_controller/metal/exceptions'
2
+ require 'active_support/deprecation'
2
3
 
3
4
  module ActionDispatch
4
5
  module Journey
@@ -12,12 +13,12 @@ module ActionDispatch
12
13
  @cache = nil
13
14
  end
14
15
 
15
- def generate(type, name, options, recall = {}, parameterize = nil)
16
- constraints = recall.merge(options)
16
+ def generate(name, options, path_parameters, parameterize = nil)
17
+ constraints = path_parameters.merge(options)
17
18
  missing_keys = []
18
19
 
19
20
  match_route(name, constraints) do |route|
20
- parameterized_parts = extract_parameterized_parts(route, options, recall, parameterize)
21
+ parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
21
22
 
22
23
  # Skip this route unless a name has been provided or it is a
23
24
  # standard Rails route since we can't determine whether an options
@@ -30,11 +31,17 @@ module ActionDispatch
30
31
  parameterized_parts.key?(key) || route.defaults.key?(key)
31
32
  end
32
33
 
34
+ defaults = route.defaults
35
+ required_parts = route.required_parts
36
+ parameterized_parts.delete_if do |key, value|
37
+ value.to_s == defaults[key].to_s && !required_parts.include?(key)
38
+ end
39
+
33
40
  return [route.format(parameterized_parts), params]
34
41
  end
35
42
 
36
- message = "No route matches #{Hash[constraints.sort].inspect}"
37
- message << " missing required keys: #{missing_keys.sort.inspect}" if name
43
+ message = "No route matches #{Hash[constraints.sort_by{|k,v| k.to_s}].inspect}"
44
+ message << " missing required keys: #{missing_keys.sort.inspect}" unless missing_keys.empty?
38
45
 
39
46
  raise ActionController::UrlGenerationError, message
40
47
  end
@@ -74,14 +81,28 @@ module ActionDispatch
74
81
  if named_routes.key?(name)
75
82
  yield named_routes[name]
76
83
  else
77
- routes = non_recursive(cache, options.to_a)
84
+ # Make sure we don't show the deprecation warning more than once
85
+ warned = false
86
+
87
+ routes = non_recursive(cache, options)
78
88
 
79
89
  hash = routes.group_by { |_, r| r.score(options) }
80
90
 
81
91
  hash.keys.sort.reverse_each do |score|
82
- next if score < 0
92
+ break if score < 0
83
93
 
84
94
  hash[score].sort_by { |i, _| i }.each do |_, route|
95
+ if name && !warned
96
+ ActiveSupport::Deprecation.warn <<-MSG.squish
97
+ You are trying to generate the URL for a named route called
98
+ #{name.inspect} but no such route was found. In the future,
99
+ this will result in an `ActionController::UrlGenerationError`
100
+ exception.
101
+ MSG
102
+
103
+ warned = true
104
+ end
105
+
85
106
  yield route
86
107
  end
87
108
  end
@@ -90,14 +111,14 @@ module ActionDispatch
90
111
 
91
112
  def non_recursive(cache, options)
92
113
  routes = []
93
- stack = [cache]
114
+ queue = [cache]
94
115
 
95
- while stack.any?
96
- c = stack.shift
116
+ while queue.any?
117
+ c = queue.shift
97
118
  routes.concat(c[:___routes]) if c.key?(:___routes)
98
119
 
99
120
  options.each do |pair|
100
- stack << c[pair] if c.key?(pair)
121
+ queue << c[pair] if c.key?(pair)
101
122
  end
102
123
  end
103
124
 
@@ -121,14 +142,9 @@ module ActionDispatch
121
142
  def possibles(cache, options, depth = 0)
122
143
  cache.fetch(:___routes) { [] } + options.find_all { |pair|
123
144
  cache.key?(pair)
124
- }.map { |pair|
145
+ }.flat_map { |pair|
125
146
  possibles(cache[pair], options, depth + 1)
126
- }.flatten(1)
127
- end
128
-
129
- # Returns +true+ if no missing keys are present, otherwise +false+.
130
- def verify_required_parts!(route, parts)
131
- missing_keys(route, parts).empty?
147
+ }
132
148
  end
133
149
 
134
150
  def build_cache