actionpack 4.2.11.3 → 5.0.0.beta1

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

Potentially problematic release.


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

Files changed (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +379 -462
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller.rb +0 -2
  6. data/lib/abstract_controller/base.rb +17 -32
  7. data/lib/abstract_controller/callbacks.rb +52 -19
  8. data/lib/abstract_controller/collector.rb +4 -9
  9. data/lib/abstract_controller/helpers.rb +2 -2
  10. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  11. data/lib/abstract_controller/rendering.rb +27 -22
  12. data/lib/abstract_controller/translation.rb +8 -7
  13. data/lib/action_controller.rb +4 -3
  14. data/lib/action_controller/api.rb +146 -0
  15. data/lib/action_controller/base.rb +6 -10
  16. data/lib/action_controller/caching.rb +1 -3
  17. data/lib/action_controller/caching/fragments.rb +48 -3
  18. data/lib/action_controller/form_builder.rb +48 -0
  19. data/lib/action_controller/log_subscriber.rb +1 -10
  20. data/lib/action_controller/metal.rb +89 -62
  21. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  22. data/lib/action_controller/metal/conditional_get.rb +65 -24
  23. data/lib/action_controller/metal/cookies.rb +0 -2
  24. data/lib/action_controller/metal/data_streaming.rb +2 -22
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  26. data/lib/action_controller/metal/exceptions.rb +11 -6
  27. data/lib/action_controller/metal/force_ssl.rb +6 -6
  28. data/lib/action_controller/metal/head.rb +14 -7
  29. data/lib/action_controller/metal/helpers.rb +9 -5
  30. data/lib/action_controller/metal/http_authentication.rb +37 -38
  31. data/lib/action_controller/metal/implicit_render.rb +23 -6
  32. data/lib/action_controller/metal/instrumentation.rb +0 -1
  33. data/lib/action_controller/metal/live.rb +17 -55
  34. data/lib/action_controller/metal/mime_responds.rb +17 -37
  35. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  36. data/lib/action_controller/metal/redirecting.rb +32 -9
  37. data/lib/action_controller/metal/renderers.rb +10 -8
  38. data/lib/action_controller/metal/rendering.rb +38 -6
  39. data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
  40. data/lib/action_controller/metal/rescue.rb +2 -4
  41. data/lib/action_controller/metal/streaming.rb +4 -4
  42. data/lib/action_controller/metal/strong_parameters.rb +231 -78
  43. data/lib/action_controller/metal/testing.rb +1 -12
  44. data/lib/action_controller/metal/url_for.rb +12 -5
  45. data/lib/action_controller/renderer.rb +111 -0
  46. data/lib/action_controller/template_assertions.rb +9 -0
  47. data/lib/action_controller/test_case.rb +267 -363
  48. data/lib/action_dispatch.rb +2 -1
  49. data/lib/action_dispatch/http/cache.rb +23 -26
  50. data/lib/action_dispatch/http/filter_parameters.rb +6 -8
  51. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  52. data/lib/action_dispatch/http/headers.rb +28 -11
  53. data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
  54. data/lib/action_dispatch/http/mime_type.rb +92 -61
  55. data/lib/action_dispatch/http/mime_types.rb +1 -4
  56. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  57. data/lib/action_dispatch/http/parameters.rb +45 -41
  58. data/lib/action_dispatch/http/request.rb +146 -82
  59. data/lib/action_dispatch/http/response.rb +180 -99
  60. data/lib/action_dispatch/http/url.rb +117 -8
  61. data/lib/action_dispatch/journey/formatter.rb +34 -28
  62. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  63. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  64. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  65. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  66. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  67. data/lib/action_dispatch/journey/path/pattern.rb +37 -41
  68. data/lib/action_dispatch/journey/route.rb +71 -17
  69. data/lib/action_dispatch/journey/router.rb +5 -6
  70. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  71. data/lib/action_dispatch/journey/routes.rb +14 -15
  72. data/lib/action_dispatch/journey/visitors.rb +86 -43
  73. data/lib/action_dispatch/middleware/cookies.rb +184 -135
  74. data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
  75. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
  76. data/lib/action_dispatch/middleware/flash.rb +61 -45
  77. data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
  78. data/lib/action_dispatch/middleware/params_parser.rb +30 -46
  79. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  80. data/lib/action_dispatch/middleware/reloader.rb +2 -4
  81. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  82. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  83. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  84. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  85. data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
  86. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  87. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  88. data/lib/action_dispatch/middleware/ssl.rb +93 -36
  89. data/lib/action_dispatch/middleware/stack.rb +43 -48
  90. data/lib/action_dispatch/middleware/static.rb +52 -40
  91. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  92. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  93. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  94. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  95. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  96. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  97. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  98. data/lib/action_dispatch/railtie.rb +0 -2
  99. data/lib/action_dispatch/request/session.rb +66 -34
  100. data/lib/action_dispatch/request/utils.rb +51 -19
  101. data/lib/action_dispatch/routing.rb +3 -8
  102. data/lib/action_dispatch/routing/inspector.rb +6 -30
  103. data/lib/action_dispatch/routing/mapper.rb +447 -322
  104. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  105. data/lib/action_dispatch/routing/redirection.rb +3 -3
  106. data/lib/action_dispatch/routing/route_set.rb +124 -227
  107. data/lib/action_dispatch/routing/url_for.rb +27 -10
  108. data/lib/action_dispatch/testing/assertions.rb +1 -1
  109. data/lib/action_dispatch/testing/assertions/response.rb +27 -9
  110. data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
  111. data/lib/action_dispatch/testing/integration.rb +237 -76
  112. data/lib/action_dispatch/testing/test_process.rb +5 -5
  113. data/lib/action_dispatch/testing/test_request.rb +12 -21
  114. data/lib/action_dispatch/testing/test_response.rb +1 -4
  115. data/lib/action_pack.rb +1 -1
  116. data/lib/action_pack/gem_version.rb +4 -4
  117. metadata +26 -25
  118. data/lib/action_controller/metal/hide_actions.rb +0 -40
  119. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  120. data/lib/action_controller/middleware.rb +0 -39
  121. data/lib/action_controller/model_naming.rb +0 -12
  122. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  123. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  124. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  125. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,32 +1,64 @@
1
1
  module ActionDispatch
2
- class Request < Rack::Request
2
+ class Request
3
3
  class Utils # :nodoc:
4
4
 
5
5
  mattr_accessor :perform_deep_munge
6
6
  self.perform_deep_munge = true
7
7
 
8
- class << self
9
- # Remove nils from the params hash
10
- def deep_munge(hash, keys = [])
11
- return hash unless perform_deep_munge
8
+ def self.normalize_encode_params(params)
9
+ if perform_deep_munge
10
+ NoNilParamEncoder.normalize_encode_params params
11
+ else
12
+ ParamEncoder.normalize_encode_params params
13
+ end
14
+ end
15
+
16
+ def self.check_param_encoding(params)
17
+ case params
18
+ when Array
19
+ params.each { |element| check_param_encoding(element) }
20
+ when Hash
21
+ params.each_value { |value| check_param_encoding(value) }
22
+ when String
23
+ unless params.valid_encoding?
24
+ # Raise Rack::Utils::InvalidParameterError for consistency with Rack.
25
+ # ActionDispatch::Request#GET will re-raise as a BadRequest error.
26
+ raise Rack::Utils::InvalidParameterError, "Non UTF-8 value: #{params}"
27
+ end
28
+ end
29
+ end
12
30
 
13
- hash.each do |k, v|
14
- keys << k
15
- case v
16
- when Array
17
- v.grep(Hash) { |x| deep_munge(x, keys) }
18
- v.compact!
19
- if v.empty?
20
- hash[k] = nil
21
- ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys)
22
- end
23
- when Hash
24
- deep_munge(v, keys)
31
+ class ParamEncoder # :nodoc:
32
+ # Convert nested Hash to HashWithIndifferentAccess.
33
+ #
34
+ def self.normalize_encode_params(params)
35
+ case params
36
+ when Array
37
+ handle_array params
38
+ when Hash
39
+ if params.has_key?(:tempfile)
40
+ ActionDispatch::Http::UploadedFile.new(params)
41
+ else
42
+ params.each_with_object({}) do |(key, val), new_hash|
43
+ new_hash[key] = normalize_encode_params(val)
44
+ end.with_indifferent_access
25
45
  end
26
- keys.pop
46
+ else
47
+ params
27
48
  end
49
+ end
50
+
51
+ def self.handle_array(params)
52
+ params.map! { |el| normalize_encode_params(el) }
53
+ end
54
+ end
28
55
 
29
- hash
56
+ # Remove nils from the params hash
57
+ class NoNilParamEncoder < ParamEncoder # :nodoc:
58
+ def self.handle_array(params)
59
+ list = super
60
+ list.compact!
61
+ list
30
62
  end
31
63
  end
32
64
  end
@@ -1,8 +1,3 @@
1
- # encoding: UTF-8
2
- require 'active_support/core_ext/object/to_param'
3
- require 'active_support/core_ext/regexp'
4
- require 'active_support/dependencies/autoload'
5
-
6
1
  module ActionDispatch
7
2
  # The routing module provides URL rewriting in native Ruby. It's a way to
8
3
  # redirect incoming requests to controllers and actions. This replaces
@@ -58,7 +53,7 @@ module ActionDispatch
58
53
  # resources :posts, :comments
59
54
  # end
60
55
  #
61
- # Alternately, you can add prefixes to your path without using a separate
56
+ # Alternatively, you can add prefixes to your path without using a separate
62
57
  # directory by using +scope+. +scope+ takes additional options which
63
58
  # apply to all enclosed routes.
64
59
  #
@@ -151,6 +146,7 @@ module ActionDispatch
151
146
  # get 'geocode/:postalcode' => :show, constraints: {
152
147
  # postalcode: /\d{5}(-\d{4})?/
153
148
  # }
149
+ # end
154
150
  #
155
151
  # Constraints can include the 'ignorecase' and 'extended syntax' regular
156
152
  # expression modifiers:
@@ -232,7 +228,6 @@ module ActionDispatch
232
228
  # def send_to_jail
233
229
  # get '/jail'
234
230
  # assert_response :success
235
- # assert_template "jail/front"
236
231
  # end
237
232
  #
238
233
  # def goes_to_login
@@ -242,7 +237,7 @@ module ActionDispatch
242
237
  #
243
238
  # == View a list of all your routes
244
239
  #
245
- # rake routes
240
+ # rails routes
246
241
  #
247
242
  # Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
248
243
  #
@@ -16,10 +16,6 @@ module ActionDispatch
16
16
  app.app
17
17
  end
18
18
 
19
- def verb
20
- super.source.gsub(/[$^]/, '')
21
- end
22
-
23
19
  def path
24
20
  super.spec.to_s
25
21
  end
@@ -28,23 +24,6 @@ module ActionDispatch
28
24
  super.to_s
29
25
  end
30
26
 
31
- def regexp
32
- __getobj__.path.to_regexp
33
- end
34
-
35
- def json_regexp
36
- str = regexp.inspect.
37
- sub('\\A' , '^').
38
- sub('\\Z' , '$').
39
- sub('\\z' , '$').
40
- sub(/^\// , '').
41
- sub(/\/[a-z]*$/ , '').
42
- gsub(/\(\?#.+\)/ , '').
43
- gsub(/\(\?-\w+:/ , '(').
44
- gsub(/\s/ , '')
45
- Regexp.new(str).source
46
- end
47
-
48
27
  def reqs
49
28
  @reqs ||= begin
50
29
  reqs = endpoint
@@ -62,7 +41,7 @@ module ActionDispatch
62
41
  end
63
42
 
64
43
  def internal?
65
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
44
+ controller.to_s =~ %r{\Arails/(info|mailers|welcome)}
66
45
  end
67
46
 
68
47
  def engine?
@@ -114,16 +93,13 @@ module ActionDispatch
114
93
  def collect_routes(routes)
115
94
  routes.collect do |route|
116
95
  RouteWrapper.new(route)
117
- end.reject do |route|
118
- route.internal?
119
- end.collect do |route|
96
+ end.reject(&:internal?).collect do |route|
120
97
  collect_engine_routes(route)
121
98
 
122
- { name: route.name,
123
- verb: route.verb,
124
- path: route.path,
125
- reqs: route.reqs,
126
- regexp: route.json_regexp }
99
+ { name: route.name,
100
+ verb: route.verb,
101
+ path: route.path,
102
+ reqs: route.reqs }
127
103
  end
128
104
  end
129
105
 
@@ -1,24 +1,23 @@
1
- require 'active_support/core_ext/hash/except'
2
1
  require 'active_support/core_ext/hash/reverse_merge'
3
2
  require 'active_support/core_ext/hash/slice'
4
3
  require 'active_support/core_ext/enumerable'
5
4
  require 'active_support/core_ext/array/extract_options'
6
- require 'active_support/core_ext/module/remove_method'
7
- require 'active_support/core_ext/string/filters'
8
- require 'active_support/inflector'
5
+ require 'active_support/core_ext/regexp'
9
6
  require 'action_dispatch/routing/redirection'
10
7
  require 'action_dispatch/routing/endpoint'
11
- require 'active_support/deprecation'
12
8
 
13
9
  module ActionDispatch
14
10
  module Routing
15
11
  class Mapper
16
12
  URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
17
13
 
18
- class Constraints < Endpoint #:nodoc:
14
+ class Constraints < Routing::Endpoint #:nodoc:
19
15
  attr_reader :app, :constraints
20
16
 
21
- def initialize(app, constraints, dispatcher_p)
17
+ SERVE = ->(app, req) { app.serve req }
18
+ CALL = ->(app, req) { app.call req.env }
19
+
20
+ def initialize(app, constraints, strategy)
22
21
  # Unwrap Constraints objects. I don't actually think it's possible
23
22
  # to pass a Constraints object to this constructor, but there were
24
23
  # multiple places that kept testing children of this object. I
@@ -28,12 +27,12 @@ module ActionDispatch
28
27
  app = app.app
29
28
  end
30
29
 
31
- @dispatcher = dispatcher_p
30
+ @strategy = strategy
32
31
 
33
32
  @app, @constraints, = app, constraints
34
33
  end
35
34
 
36
- def dispatcher?; @dispatcher; end
35
+ def dispatcher?; @strategy == SERVE; end
37
36
 
38
37
  def matches?(req)
39
38
  @constraints.all? do |constraint|
@@ -45,11 +44,7 @@ module ActionDispatch
45
44
  def serve(req)
46
45
  return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
47
46
 
48
- if dispatcher?
49
- @app.serve req
50
- else
51
- @app.call req.env
52
- end
47
+ @strategy.call @app, req
53
48
  end
54
49
 
55
50
  private
@@ -60,103 +55,169 @@ module ActionDispatch
60
55
 
61
56
  class Mapping #:nodoc:
62
57
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
63
- OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
64
58
 
65
- attr_reader :requirements, :conditions, :defaults
66
- attr_reader :to, :default_controller, :default_action, :as, :anchor
59
+ attr_reader :requirements, :defaults
60
+ attr_reader :to, :default_controller, :default_action
61
+ attr_reader :required_defaults, :ast
67
62
 
68
- def self.build(scope, set, path, as, options)
63
+ def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
69
64
  options = scope[:options].merge(options) if scope[:options]
70
65
 
71
- options.delete :only
72
- options.delete :except
73
- options.delete :shallow_path
74
- options.delete :shallow_prefix
75
- options.delete :shallow
66
+ defaults = (scope[:defaults] || {}).dup
67
+ scope_constraints = scope[:constraints] || {}
76
68
 
77
- defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
69
+ new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
70
+ end
78
71
 
79
- new scope, set, path, defaults, as, options
72
+ def self.check_via(via)
73
+ if via.empty?
74
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
75
+ "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
76
+ "If you want to expose your action to GET, use `get` in the router:\n" \
77
+ " Instead of: match \"controller#action\"\n" \
78
+ " Do: get \"controller#action\""
79
+ raise ArgumentError, msg
80
+ end
81
+ via
80
82
  end
81
83
 
82
- def initialize(scope, set, path, defaults, as, options)
83
- @requirements, @conditions = {}, {}
84
- @defaults = defaults
85
- @set = set
84
+ def self.normalize_path(path, format)
85
+ path = Mapper.normalize_path(path)
86
+
87
+ if format == true
88
+ "#{path}.:format"
89
+ elsif optional_format?(path, format)
90
+ "#{path}(.:format)"
91
+ else
92
+ path
93
+ end
94
+ end
86
95
 
87
- @to = options.delete :to
88
- @default_controller = options.delete(:controller) || scope[:controller]
89
- @default_action = options.delete(:action) || scope[:action]
90
- @as = as
91
- @anchor = options.delete :anchor
96
+ def self.optional_format?(path, format)
97
+ format != false && !path.include?(':format') && !path.end_with?('/')
98
+ end
92
99
 
93
- formatted = options.delete :format
94
- via = Array(options.delete(:via) { [] })
95
- options_constraints = options.delete :constraints
100
+ def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
101
+ @defaults = defaults
102
+ @set = set
96
103
 
97
- path = normalize_path! path, formatted
98
- ast = path_ast path
99
- path_params = path_params ast
104
+ @to = to
105
+ @default_controller = controller
106
+ @default_action = default_action
107
+ @ast = ast
108
+ @anchor = anchor
109
+ @via = via
100
110
 
101
- options = normalize_options!(options, formatted, path_params, ast, scope[:module])
111
+ path_params = ast.find_all(&:symbol?).map(&:to_sym)
102
112
 
113
+ options = add_wildcard_options(options, formatted, ast)
103
114
 
104
- split_constraints(path_params, scope[:constraints]) if scope[:constraints]
105
- constraints = constraints(options, path_params)
115
+ options = normalize_options!(options, path_params, modyoule)
106
116
 
107
- split_constraints path_params, constraints
117
+ split_options = constraints(options, path_params)
108
118
 
109
- @blocks = blocks(options_constraints, scope[:blocks])
119
+ constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
110
120
 
111
121
  if options_constraints.is_a?(Hash)
112
- split_constraints path_params, options_constraints
113
- options_constraints.each do |key, default|
114
- if URL_OPTIONS.include?(key) && (String === default || Integer === default)
115
- @defaults[key] ||= default
116
- end
117
- end
122
+ @defaults = Hash[options_constraints.find_all { |key, default|
123
+ URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
124
+ }].merge @defaults
125
+ @blocks = blocks
126
+ constraints.merge! options_constraints
127
+ else
128
+ @blocks = blocks(options_constraints)
118
129
  end
119
130
 
120
- normalize_format!(formatted)
131
+ requirements, conditions = split_constraints path_params, constraints
132
+ verify_regexp_requirements requirements.map(&:last).grep(Regexp)
121
133
 
122
- @conditions[:path_info] = path
123
- @conditions[:parsed_path_info] = ast
134
+ formats = normalize_format(formatted)
124
135
 
125
- add_request_method(via, @conditions)
126
- normalize_defaults!(options)
136
+ @requirements = formats[:requirements].merge Hash[requirements]
137
+ @conditions = Hash[conditions]
138
+ @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
139
+
140
+ @required_defaults = (split_options[:required_defaults] || []).map(&:first)
127
141
  end
128
142
 
129
- def to_route
130
- [ app(@blocks), conditions, requirements, defaults, as, anchor ]
143
+ def make_route(name, precedence)
144
+ route = Journey::Route.new(name,
145
+ application,
146
+ path,
147
+ conditions,
148
+ required_defaults,
149
+ defaults,
150
+ request_method,
151
+ precedence)
152
+
153
+ route
131
154
  end
132
155
 
133
- private
156
+ def application
157
+ app(@blocks)
158
+ end
134
159
 
135
- def normalize_path!(path, format)
136
- path = Mapper.normalize_path(path)
160
+ def path
161
+ build_path @ast, requirements, @anchor
162
+ end
137
163
 
138
- if format == true
139
- "#{path}.:format"
140
- elsif optional_format?(path, format)
141
- "#{path}(.:format)"
142
- else
143
- path
144
- end
145
- end
164
+ def conditions
165
+ build_conditions @conditions, @set.request_class
166
+ end
146
167
 
147
- def optional_format?(path, format)
148
- format != false && path !~ OPTIONAL_FORMAT_REGEX
168
+ def build_conditions(current_conditions, request_class)
169
+ conditions = current_conditions.dup
170
+
171
+ conditions.keep_if do |k, _|
172
+ request_class.public_method_defined?(k)
149
173
  end
174
+ end
175
+ private :build_conditions
176
+
177
+ def request_method
178
+ @via.map { |x| Journey::Route.verb_matcher(x) }
179
+ end
180
+ private :request_method
181
+
182
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
183
+
184
+ def build_path(ast, requirements, anchor)
185
+ pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
186
+
187
+ # Get all the symbol nodes followed by literals that are not the
188
+ # dummy node.
189
+ symbols = ast.find_all { |n|
190
+ n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
191
+ }.map(&:left)
192
+
193
+ # Get all the symbol nodes preceded by literals.
194
+ symbols.concat ast.find_all { |n|
195
+ n.cat? && n.left.literal? && n.right.cat? && n.right.left.symbol?
196
+ }.map { |n| n.right.left }
150
197
 
151
- def normalize_options!(options, formatted, path_params, path_ast, modyoule)
198
+ symbols.each { |x|
199
+ x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
200
+ }
201
+
202
+ pattern
203
+ end
204
+ private :build_path
205
+
206
+
207
+ private
208
+ def add_wildcard_options(options, formatted, path_ast)
152
209
  # Add a constraint for wildcard route to make it non-greedy and match the
153
210
  # optional format part of the route by default
154
211
  if formatted != false
155
- path_ast.grep(Journey::Nodes::Star) do |node|
156
- options[node.name.to_sym] ||= /.+?/
157
- end
212
+ path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
213
+ hash[node.name.to_sym] ||= /.+?/
214
+ }.merge options
215
+ else
216
+ options
158
217
  end
218
+ end
159
219
 
220
+ def normalize_options!(options, path_params, modyoule)
160
221
  if path_params.include?(:controller)
161
222
  raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
162
223
 
@@ -181,74 +242,54 @@ module ActionDispatch
181
242
  end
182
243
 
183
244
  def split_constraints(path_params, constraints)
184
- constraints.each_pair do |key, requirement|
185
- if path_params.include?(key) || key == :controller
186
- verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
187
- @requirements[key] = requirement
188
- else
189
- @conditions[key] = requirement
190
- end
191
- end
192
- end
193
-
194
- def normalize_format!(formatted)
195
- if formatted == true
196
- @requirements[:format] ||= /.+/
197
- elsif Regexp === formatted
198
- @requirements[:format] = formatted
199
- @defaults[:format] = nil
200
- elsif String === formatted
201
- @requirements[:format] = Regexp.compile(formatted)
202
- @defaults[:format] = formatted
245
+ constraints.partition do |key, requirement|
246
+ path_params.include?(key) || key == :controller
203
247
  end
204
248
  end
205
249
 
206
- def verify_regexp_requirement(requirement)
207
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
208
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
209
- end
210
-
211
- if requirement.multiline?
212
- raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
250
+ def normalize_format(formatted)
251
+ case formatted
252
+ when true
253
+ { requirements: { format: /.+/ },
254
+ defaults: {} }
255
+ when Regexp
256
+ { requirements: { format: formatted },
257
+ defaults: { format: nil } }
258
+ when String
259
+ { requirements: { format: Regexp.compile(formatted) },
260
+ defaults: { format: formatted } }
261
+ else
262
+ { requirements: { }, defaults: { } }
213
263
  end
214
264
  end
215
265
 
216
- def normalize_defaults!(options)
217
- options.each_pair do |key, default|
218
- unless Regexp === default
219
- @defaults[key] = default
266
+ def verify_regexp_requirements(requirements)
267
+ requirements.each do |requirement|
268
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
269
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
220
270
  end
221
- end
222
- end
223
271
 
224
- def verify_callable_constraint(callable_constraint)
225
- unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
226
- raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
272
+ if requirement.multiline?
273
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
274
+ end
227
275
  end
228
276
  end
229
277
 
230
- def add_request_method(via, conditions)
231
- return if via == [:all]
232
-
233
- if via.empty?
234
- msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
235
- "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
236
- "If you want to expose your action to GET, use `get` in the router:\n" \
237
- " Instead of: match \"controller#action\"\n" \
238
- " Do: get \"controller#action\""
239
- raise ArgumentError, msg
240
- end
241
-
242
- conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
278
+ def normalize_defaults(options)
279
+ Hash[options.reject { |_, default| Regexp === default }]
243
280
  end
244
281
 
245
282
  def app(blocks)
246
- if to.respond_to?(:call)
247
- Constraints.new(to, blocks, false)
248
- elsif blocks.any?
249
- Constraints.new(dispatcher(defaults), blocks, true)
283
+ if to.is_a?(Class) && to < ActionController::Metal
284
+ Routing::RouteSet::StaticDispatcher.new to
250
285
  else
251
- dispatcher(defaults)
286
+ if to.respond_to?(:call)
287
+ Constraints.new(to, blocks, Constraints::CALL)
288
+ elsif blocks.any?
289
+ Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
290
+ else
291
+ dispatcher(defaults.key?(:controller))
292
+ end
252
293
  end
253
294
  end
254
295
 
@@ -280,22 +321,8 @@ module ActionDispatch
280
321
  end
281
322
 
282
323
  def split_to(to)
283
- case to
284
- when Symbol
285
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
286
- Defining a route where `to` is a symbol is deprecated.
287
- Please change `to: :#{to}` to `action: :#{to}`.
288
- MSG
289
-
290
- [nil, to.to_s]
291
- when /#/ then to.split('#')
292
- when String
293
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
294
- Defining a route where `to` is a controller without an action is deprecated.
295
- Please change `to: '#{to}'` to `controller: '#{to}'`.
296
- MSG
297
-
298
- [to, nil]
324
+ if to =~ /#/
325
+ to.split('#')
299
326
  else
300
327
  []
301
328
  end
@@ -320,40 +347,29 @@ module ActionDispatch
320
347
  yield
321
348
  end
322
349
 
323
- def blocks(options_constraints, scope_blocks)
324
- if options_constraints && !options_constraints.is_a?(Hash)
325
- verify_callable_constraint(options_constraints)
326
- [options_constraints]
327
- else
328
- scope_blocks || []
350
+ def blocks(callable_constraint)
351
+ unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
352
+ raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
329
353
  end
354
+ [callable_constraint]
330
355
  end
331
356
 
332
357
  def constraints(options, path_params)
333
- constraints = {}
334
- required_defaults = []
335
- options.each_pair do |key, option|
358
+ options.group_by do |key, option|
336
359
  if Regexp === option
337
- constraints[key] = option
360
+ :constraints
338
361
  else
339
- required_defaults << key unless path_params.include?(key)
362
+ if path_params.include?(key)
363
+ :path_params
364
+ else
365
+ :required_defaults
366
+ end
340
367
  end
341
368
  end
342
- @conditions[:required_defaults] = required_defaults
343
- constraints
344
369
  end
345
370
 
346
- def path_params(ast)
347
- ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
348
- end
349
-
350
- def path_ast(path)
351
- parser = Journey::Parser.new
352
- parser.parse path
353
- end
354
-
355
- def dispatcher(defaults)
356
- @set.dispatcher defaults
371
+ def dispatcher(raise_on_name_error)
372
+ Routing::RouteSet::Dispatcher.new raise_on_name_error
357
373
  end
358
374
  end
359
375
 
@@ -385,7 +401,8 @@ module ActionDispatch
385
401
  # because this means it will be matched first. As this is the most popular route
386
402
  # of most Rails applications, this is beneficial.
387
403
  def root(options = {})
388
- match '/', { :as => :root, :via => :get }.merge!(options)
404
+ name = has_named_route?(:root) ? nil : :root
405
+ match '/', { as: name, via: :get }.merge!(options)
389
406
  end
390
407
 
391
408
  # Matches a url pattern to one or more routes.
@@ -435,7 +452,7 @@ module ActionDispatch
435
452
  # A pattern can also point to a +Rack+ endpoint i.e. anything that
436
453
  # responds to +call+:
437
454
  #
438
- # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
455
+ # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
439
456
  # match 'photos/:id', to: PhotoRackApp, via: :get
440
457
  # # Yes, controller actions are just rack endpoints
441
458
  # match 'photos/:id', to: PhotosController.action(:show), via: :get
@@ -460,6 +477,21 @@ module ActionDispatch
460
477
  # dynamic segment used to generate the routes).
461
478
  # You can access that segment from your controller using
462
479
  # <tt>params[<:param>]</tt>.
480
+ # In your router:
481
+ #
482
+ # resources :user, param: :name
483
+ #
484
+ # You can override <tt>ActiveRecord::Base#to_param</tt> of a related
485
+ # model to construct a URL:
486
+ #
487
+ # class User < ActiveRecord::Base
488
+ # def to_param
489
+ # name
490
+ # end
491
+ # end
492
+ #
493
+ # user = User.find_by(name: 'Phusion')
494
+ # user_path(user) # => "/users/Phusion"
463
495
  #
464
496
  # [:path]
465
497
  # The path prefix for the routes.
@@ -487,7 +519,7 @@ module ActionDispatch
487
519
  # +call+ or a string representing a controller's action.
488
520
  #
489
521
  # match 'path', to: 'controller#action', via: :get
490
- # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
522
+ # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
491
523
  # match 'path', to: RackApp, via: :get
492
524
  #
493
525
  # [:on]
@@ -568,17 +600,20 @@ module ActionDispatch
568
600
  def mount(app, options = nil)
569
601
  if options
570
602
  path = options.delete(:at)
571
- else
572
- unless Hash === app
573
- raise ArgumentError, "must be called with mount point"
574
- end
575
-
603
+ elsif Hash === app
576
604
  options = app
577
605
  app, path = options.find { |k, _| k.respond_to?(:call) }
578
606
  options.delete(app) if app
579
607
  end
580
608
 
581
- raise "A rack application must be specified" unless path
609
+ raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
610
+ raise ArgumentError, <<-MSG.strip_heredoc unless path
611
+ Must be called with mount point
612
+
613
+ mount SomeRackApp, at: "some_route"
614
+ or
615
+ mount(SomeRackApp => "some_route")
616
+ MSG
582
617
 
583
618
  rails_app = rails_app? app
584
619
  options[:as] ||= app_name(app, rails_app)
@@ -605,7 +640,7 @@ module ActionDispatch
605
640
 
606
641
  # Query if the following named route was already defined.
607
642
  def has_named_route?(name)
608
- @set.named_routes.routes[name.to_sym]
643
+ @set.named_routes.key? name
609
644
  end
610
645
 
611
646
  private
@@ -688,7 +723,11 @@ module ActionDispatch
688
723
  def map_method(method, args, &block)
689
724
  options = args.extract_options!
690
725
  options[:via] = method
691
- match(*args, options, &block)
726
+ if options.key?(:defaults)
727
+ defaults(options.delete(:defaults)) { match(*args, options, &block) }
728
+ else
729
+ match(*args, options, &block)
730
+ end
692
731
  self
693
732
  end
694
733
  end
@@ -792,7 +831,7 @@ module ActionDispatch
792
831
 
793
832
  if options[:constraints].is_a?(Hash)
794
833
  defaults = options[:constraints].select do |k, v|
795
- URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
834
+ URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
796
835
  end
797
836
 
798
837
  (options[:defaults] ||= {}).reverse_merge!(defaults)
@@ -800,16 +839,25 @@ module ActionDispatch
800
839
  block, options[:constraints] = options[:constraints], {}
801
840
  end
802
841
 
842
+ if options.key?(:only) || options.key?(:except)
843
+ scope[:action_options] = { only: options.delete(:only),
844
+ except: options.delete(:except) }
845
+ end
846
+
847
+ if options.key? :anchor
848
+ raise ArgumentError, 'anchor is ignored unless passed to `match`'
849
+ end
850
+
803
851
  @scope.options.each do |option|
804
852
  if option == :blocks
805
853
  value = block
806
854
  elsif option == :options
807
855
  value = options
808
856
  else
809
- value = options.delete(option)
857
+ value = options.delete(option) { POISON }
810
858
  end
811
859
 
812
- if value
860
+ unless POISON == value
813
861
  scope[option] = send("merge_#{option}_scope", @scope[option], value)
814
862
  end
815
863
  end
@@ -821,14 +869,18 @@ module ActionDispatch
821
869
  @scope = @scope.parent
822
870
  end
823
871
 
872
+ POISON = Object.new # :nodoc:
873
+
824
874
  # Scopes routes to a specific controller
825
875
  #
826
876
  # controller "food" do
827
- # match "bacon", action: "bacon"
877
+ # match "bacon", action: :bacon, via: :get
828
878
  # end
829
- def controller(controller, options={})
830
- options[:controller] = controller
831
- scope(options) { yield }
879
+ def controller(controller)
880
+ @scope = @scope.new(controller: controller)
881
+ yield
882
+ ensure
883
+ @scope = @scope.parent
832
884
  end
833
885
 
834
886
  # Scopes routes to a specific namespace. For example:
@@ -874,13 +926,14 @@ module ActionDispatch
874
926
 
875
927
  defaults = {
876
928
  module: path,
877
- path: options.fetch(:path, path),
878
929
  as: options.fetch(:as, path),
879
930
  shallow_path: options.fetch(:path, path),
880
931
  shallow_prefix: options.fetch(:as, path)
881
932
  }
882
933
 
883
- scope(defaults.merge!(options)) { yield }
934
+ path_scope(options.delete(:path) { path }) do
935
+ scope(defaults.merge!(options)) { yield }
936
+ end
884
937
  end
885
938
 
886
939
  # === Parameter Restriction
@@ -917,7 +970,7 @@ module ActionDispatch
917
970
  #
918
971
  # Requests to routes can be constrained based on specific criteria:
919
972
  #
920
- # constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
973
+ # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
921
974
  # resources :iphones
922
975
  # end
923
976
  #
@@ -948,7 +1001,10 @@ module ActionDispatch
948
1001
  # end
949
1002
  # Using this, the +:id+ parameter here will default to 'home'.
950
1003
  def defaults(defaults = {})
951
- scope(:defaults => defaults) { yield }
1004
+ @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
1005
+ yield
1006
+ ensure
1007
+ @scope = @scope.parent
952
1008
  end
953
1009
 
954
1010
  private
@@ -980,6 +1036,14 @@ module ActionDispatch
980
1036
  child
981
1037
  end
982
1038
 
1039
+ def merge_via_scope(parent, child) #:nodoc:
1040
+ child
1041
+ end
1042
+
1043
+ def merge_format_scope(parent, child) #:nodoc:
1044
+ child
1045
+ end
1046
+
983
1047
  def merge_path_names_scope(parent, child) #:nodoc:
984
1048
  merge_options_scope(parent, child)
985
1049
  end
@@ -999,16 +1063,12 @@ module ActionDispatch
999
1063
  end
1000
1064
 
1001
1065
  def merge_options_scope(parent, child) #:nodoc:
1002
- (parent || {}).except(*override_keys(child)).merge!(child)
1066
+ (parent || {}).merge(child)
1003
1067
  end
1004
1068
 
1005
1069
  def merge_shallow_scope(parent, child) #:nodoc:
1006
1070
  child ? true : false
1007
1071
  end
1008
-
1009
- def override_keys(child) #:nodoc:
1010
- child.key?(:only) || child.key?(:except) ? [:only, :except] : []
1011
- end
1012
1072
  end
1013
1073
 
1014
1074
  # Resource routing allows you to quickly declare all of the common routes
@@ -1058,27 +1118,34 @@ module ActionDispatch
1058
1118
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1059
1119
 
1060
1120
  class Resource #:nodoc:
1061
- attr_reader :controller, :path, :options, :param
1121
+ attr_reader :controller, :path, :param
1062
1122
 
1063
- def initialize(entities, options = {})
1123
+ def initialize(entities, api_only, shallow, options = {})
1064
1124
  @name = entities.to_s
1065
1125
  @path = (options[:path] || @name).to_s
1066
1126
  @controller = (options[:controller] || @name).to_s
1067
1127
  @as = options[:as]
1068
1128
  @param = (options[:param] || :id).to_sym
1069
1129
  @options = options
1070
- @shallow = false
1130
+ @shallow = shallow
1131
+ @api_only = api_only
1132
+ @only = options.delete :only
1133
+ @except = options.delete :except
1071
1134
  end
1072
1135
 
1073
1136
  def default_actions
1074
- [:index, :create, :new, :show, :update, :destroy, :edit]
1137
+ if @api_only
1138
+ [:index, :create, :show, :update, :destroy]
1139
+ else
1140
+ [:index, :create, :new, :show, :update, :destroy, :edit]
1141
+ end
1075
1142
  end
1076
1143
 
1077
1144
  def actions
1078
- if only = @options[:only]
1079
- Array(only).map(&:to_sym)
1080
- elsif except = @options[:except]
1081
- default_actions - Array(except).map(&:to_sym)
1145
+ if @only
1146
+ Array(@only).map(&:to_sym)
1147
+ elsif @except
1148
+ default_actions - Array(@except).map(&:to_sym)
1082
1149
  else
1083
1150
  default_actions
1084
1151
  end
@@ -1105,7 +1172,7 @@ module ActionDispatch
1105
1172
  end
1106
1173
 
1107
1174
  def resource_scope
1108
- { :controller => controller }
1175
+ controller
1109
1176
  end
1110
1177
 
1111
1178
  alias :collection_scope :path
@@ -1128,17 +1195,15 @@ module ActionDispatch
1128
1195
  "#{path}/:#{nested_param}"
1129
1196
  end
1130
1197
 
1131
- def shallow=(value)
1132
- @shallow = value
1133
- end
1134
-
1135
1198
  def shallow?
1136
1199
  @shallow
1137
1200
  end
1201
+
1202
+ def singleton?; false; end
1138
1203
  end
1139
1204
 
1140
1205
  class SingletonResource < Resource #:nodoc:
1141
- def initialize(entities, options)
1206
+ def initialize(entities, api_only, shallow, options)
1142
1207
  super
1143
1208
  @as = nil
1144
1209
  @controller = (options[:controller] || plural).to_s
@@ -1146,7 +1211,11 @@ module ActionDispatch
1146
1211
  end
1147
1212
 
1148
1213
  def default_actions
1149
- [:show, :create, :update, :destroy, :new, :edit]
1214
+ if @api_only
1215
+ [:show, :create, :update, :destroy]
1216
+ else
1217
+ [:show, :create, :update, :destroy, :new, :edit]
1218
+ end
1150
1219
  end
1151
1220
 
1152
1221
  def plural
@@ -1162,6 +1231,8 @@ module ActionDispatch
1162
1231
 
1163
1232
  alias :member_scope :path
1164
1233
  alias :nested_scope :path
1234
+
1235
+ def singleton?; true; end
1165
1236
  end
1166
1237
 
1167
1238
  def resources_path_names(options)
@@ -1196,20 +1267,23 @@ module ActionDispatch
1196
1267
  return self
1197
1268
  end
1198
1269
 
1199
- resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
1200
- yield if block_given?
1270
+ with_scope_level(:resource) do
1271
+ options = apply_action_options options
1272
+ resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1273
+ yield if block_given?
1201
1274
 
1202
- concerns(options[:concerns]) if options[:concerns]
1275
+ concerns(options[:concerns]) if options[:concerns]
1203
1276
 
1204
- collection do
1205
- post :create
1206
- end if parent_resource.actions.include?(:create)
1277
+ collection do
1278
+ post :create
1279
+ end if parent_resource.actions.include?(:create)
1207
1280
 
1208
- new do
1209
- get :new
1210
- end if parent_resource.actions.include?(:new)
1281
+ new do
1282
+ get :new
1283
+ end if parent_resource.actions.include?(:new)
1211
1284
 
1212
- set_member_mappings_for_resource
1285
+ set_member_mappings_for_resource
1286
+ end
1213
1287
  end
1214
1288
 
1215
1289
  self
@@ -1354,21 +1428,24 @@ module ActionDispatch
1354
1428
  return self
1355
1429
  end
1356
1430
 
1357
- resource_scope(:resources, Resource.new(resources.pop, options)) do
1358
- yield if block_given?
1431
+ with_scope_level(:resources) do
1432
+ options = apply_action_options options
1433
+ resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1434
+ yield if block_given?
1359
1435
 
1360
- concerns(options[:concerns]) if options[:concerns]
1436
+ concerns(options[:concerns]) if options[:concerns]
1361
1437
 
1362
- collection do
1363
- get :index if parent_resource.actions.include?(:index)
1364
- post :create if parent_resource.actions.include?(:create)
1365
- end
1438
+ collection do
1439
+ get :index if parent_resource.actions.include?(:index)
1440
+ post :create if parent_resource.actions.include?(:create)
1441
+ end
1366
1442
 
1367
- new do
1368
- get :new
1369
- end if parent_resource.actions.include?(:new)
1443
+ new do
1444
+ get :new
1445
+ end if parent_resource.actions.include?(:new)
1370
1446
 
1371
- set_member_mappings_for_resource
1447
+ set_member_mappings_for_resource
1448
+ end
1372
1449
  end
1373
1450
 
1374
1451
  self
@@ -1392,7 +1469,7 @@ module ActionDispatch
1392
1469
  end
1393
1470
 
1394
1471
  with_scope_level(:collection) do
1395
- scope(parent_resource.collection_scope) do
1472
+ path_scope(parent_resource.collection_scope) do
1396
1473
  yield
1397
1474
  end
1398
1475
  end
@@ -1416,9 +1493,11 @@ module ActionDispatch
1416
1493
 
1417
1494
  with_scope_level(:member) do
1418
1495
  if shallow?
1419
- shallow_scope(parent_resource.member_scope) { yield }
1496
+ shallow_scope {
1497
+ path_scope(parent_resource.member_scope) { yield }
1498
+ }
1420
1499
  else
1421
- scope(parent_resource.member_scope) { yield }
1500
+ path_scope(parent_resource.member_scope) { yield }
1422
1501
  end
1423
1502
  end
1424
1503
  end
@@ -1429,7 +1508,7 @@ module ActionDispatch
1429
1508
  end
1430
1509
 
1431
1510
  with_scope_level(:new) do
1432
- scope(parent_resource.new_scope(action_path(:new))) do
1511
+ path_scope(parent_resource.new_scope(action_path(:new))) do
1433
1512
  yield
1434
1513
  end
1435
1514
  end
@@ -1442,9 +1521,15 @@ module ActionDispatch
1442
1521
 
1443
1522
  with_scope_level(:nested) do
1444
1523
  if shallow? && shallow_nesting_depth >= 1
1445
- shallow_scope(parent_resource.nested_scope, nested_options) { yield }
1524
+ shallow_scope do
1525
+ path_scope(parent_resource.nested_scope) do
1526
+ scope(nested_options) { yield }
1527
+ end
1528
+ end
1446
1529
  else
1447
- scope(parent_resource.nested_scope, nested_options) { yield }
1530
+ path_scope(parent_resource.nested_scope) do
1531
+ scope(nested_options) { yield }
1532
+ end
1448
1533
  end
1449
1534
  end
1450
1535
  end
@@ -1459,18 +1544,22 @@ module ActionDispatch
1459
1544
  end
1460
1545
 
1461
1546
  def shallow
1462
- scope(:shallow => true) do
1463
- yield
1464
- end
1547
+ @scope = @scope.new(shallow: true)
1548
+ yield
1549
+ ensure
1550
+ @scope = @scope.parent
1465
1551
  end
1466
1552
 
1467
1553
  def shallow?
1468
- parent_resource.instance_of?(Resource) && @scope[:shallow]
1554
+ !parent_resource.singleton? && @scope[:shallow]
1469
1555
  end
1470
1556
 
1471
- # match 'path' => 'controller#action'
1472
- # match 'path', to: 'controller#action'
1473
- # match 'path', 'otherpath', on: :member, via: :get
1557
+ # Matches a url pattern to one or more routes.
1558
+ # For more information, see match[rdoc-ref:Base#match].
1559
+ #
1560
+ # match 'path' => 'controller#action', via: patch
1561
+ # match 'path', to: 'controller#action', via: :post
1562
+ # match 'path', 'otherpath', on: :member, via: :get
1474
1563
  def match(path, *rest)
1475
1564
  if rest.empty? && Hash === path
1476
1565
  options = path
@@ -1496,8 +1585,6 @@ module ActionDispatch
1496
1585
  paths = [path] + rest
1497
1586
  end
1498
1587
 
1499
- options[:anchor] = true unless options.key?(:anchor)
1500
-
1501
1588
  if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1502
1589
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1503
1590
  end
@@ -1506,48 +1593,85 @@ module ActionDispatch
1506
1593
  options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1507
1594
  end
1508
1595
 
1509
- paths.each do |_path|
1596
+ controller = options.delete(:controller) || @scope[:controller]
1597
+ option_path = options.delete :path
1598
+ to = options.delete :to
1599
+ via = Mapping.check_via Array(options.delete(:via) {
1600
+ @scope[:via]
1601
+ })
1602
+ formatted = options.delete(:format) { @scope[:format] }
1603
+ anchor = options.delete(:anchor) { true }
1604
+ options_constraints = options.delete(:constraints) || {}
1605
+
1606
+ path_types = paths.group_by(&:class)
1607
+ path_types.fetch(String, []).each do |_path|
1510
1608
  route_options = options.dup
1511
- route_options[:path] ||= _path if _path.is_a?(String)
1609
+ if _path && option_path
1610
+ ActiveSupport::Deprecation.warn <<-eowarn
1611
+ Specifying strings for both :path and the route path is deprecated. Change things like this:
1612
+
1613
+ match #{_path.inspect}, :path => #{option_path.inspect}
1614
+
1615
+ to this:
1512
1616
 
1513
- path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
1514
- if using_match_shorthand?(path_without_format, route_options)
1515
- route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
1516
- route_options[:to].tr!("-", "_")
1617
+ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{path.inspect}
1618
+ eowarn
1619
+ route_options[:action] = _path
1620
+ route_options[:as] = _path
1621
+ _path = option_path
1517
1622
  end
1623
+ to = get_to_from_path(_path, to, route_options[:action])
1624
+ decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1625
+ end
1518
1626
 
1519
- decomposed_match(_path, route_options)
1627
+ path_types.fetch(Symbol, []).each do |action|
1628
+ route_options = options.dup
1629
+ decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1520
1630
  end
1631
+
1521
1632
  self
1522
1633
  end
1523
1634
 
1524
- def using_match_shorthand?(path, options)
1525
- path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
1635
+ def get_to_from_path(path, to, action)
1636
+ return to if to || action
1637
+
1638
+ path_without_format = path.sub(/\(\.:format\)$/, '')
1639
+ if using_match_shorthand?(path_without_format)
1640
+ path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1641
+ else
1642
+ nil
1643
+ end
1644
+ end
1645
+
1646
+ def using_match_shorthand?(path)
1647
+ path =~ %r{^/?[-\w]+/[-\w/]+$}
1526
1648
  end
1527
1649
 
1528
- def decomposed_match(path, options) # :nodoc:
1650
+ def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
1529
1651
  if on = options.delete(:on)
1530
- send(on) { decomposed_match(path, options) }
1652
+ send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1531
1653
  else
1532
1654
  case @scope.scope_level
1533
1655
  when :resources
1534
- nested { decomposed_match(path, options) }
1656
+ nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1535
1657
  when :resource
1536
- member { decomposed_match(path, options) }
1658
+ member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1537
1659
  else
1538
- add_route(path, options)
1660
+ add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
1539
1661
  end
1540
1662
  end
1541
1663
  end
1542
1664
 
1543
- def add_route(action, options) # :nodoc:
1544
- path = path_for_action(action, options.delete(:path))
1665
+ def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
1666
+ path = path_for_action(action, _path)
1545
1667
  raise ArgumentError, "path is required" if path.blank?
1546
1668
 
1547
- action = action.to_s.dup
1669
+ action = action.to_s
1670
+
1671
+ default_action = options.delete(:action) || @scope[:action]
1548
1672
 
1549
1673
  if action =~ /^[\w\-\/]+$/
1550
- options[:action] ||= action.tr('-', '_') unless action.include?("/")
1674
+ default_action ||= action.tr('-', '_') unless action.include?("/")
1551
1675
  else
1552
1676
  action = nil
1553
1677
  end
@@ -1558,9 +1682,11 @@ module ActionDispatch
1558
1682
  name_for_action(options.delete(:as), action)
1559
1683
  end
1560
1684
 
1561
- mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
1562
- app, conditions, requirements, defaults, as, anchor = mapping.to_route
1563
- @set.add_route(app, conditions, requirements, defaults, as, anchor)
1685
+ path = Mapping.normalize_path URI.parser.escape(path), formatted
1686
+ ast = Journey::Parser.parse path
1687
+
1688
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
1689
+ @set.add_route(mapping, ast, as, anchor)
1564
1690
  end
1565
1691
 
1566
1692
  def root(path, options={})
@@ -1574,7 +1700,7 @@ module ActionDispatch
1574
1700
 
1575
1701
  if @scope.resources?
1576
1702
  with_scope_level(:root) do
1577
- scope(parent_resource.path) do
1703
+ path_scope(parent_resource.path) do
1578
1704
  super(options)
1579
1705
  end
1580
1706
  end
@@ -1619,23 +1745,20 @@ module ActionDispatch
1619
1745
  return true
1620
1746
  end
1621
1747
 
1622
- unless action_options?(options)
1623
- options.merge!(scope_action_options) if scope_action_options?
1624
- end
1625
-
1626
1748
  false
1627
1749
  end
1628
1750
 
1629
- def action_options?(options) #:nodoc:
1630
- options[:only] || options[:except]
1751
+ def apply_action_options(options) # :nodoc:
1752
+ return options if action_options? options
1753
+ options.merge scope_action_options
1631
1754
  end
1632
1755
 
1633
- def scope_action_options? #:nodoc:
1634
- @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
1756
+ def action_options?(options) #:nodoc:
1757
+ options[:only] || options[:except]
1635
1758
  end
1636
1759
 
1637
1760
  def scope_action_options #:nodoc:
1638
- @scope[:options].slice(:only, :except)
1761
+ @scope[:action_options] || {}
1639
1762
  end
1640
1763
 
1641
1764
  def resource_scope? #:nodoc:
@@ -1650,18 +1773,6 @@ module ActionDispatch
1650
1773
  @scope.nested?
1651
1774
  end
1652
1775
 
1653
- def with_exclusive_scope
1654
- begin
1655
- @scope = @scope.new(:as => nil, :path => nil)
1656
-
1657
- with_scope_level(:exclusive) do
1658
- yield
1659
- end
1660
- ensure
1661
- @scope = @scope.parent
1662
- end
1663
- end
1664
-
1665
1776
  def with_scope_level(kind)
1666
1777
  @scope = @scope.new_level(kind)
1667
1778
  yield
@@ -1669,16 +1780,11 @@ module ActionDispatch
1669
1780
  @scope = @scope.parent
1670
1781
  end
1671
1782
 
1672
- def resource_scope(kind, resource) #:nodoc:
1673
- resource.shallow = @scope[:shallow]
1783
+ def resource_scope(resource) #:nodoc:
1674
1784
  @scope = @scope.new(:scope_level_resource => resource)
1675
- @nesting.push(resource)
1676
1785
 
1677
- with_scope_level(kind) do
1678
- scope(parent_resource.resource_scope) { yield }
1679
- end
1786
+ controller(resource.resource_scope) { yield }
1680
1787
  ensure
1681
- @nesting.pop
1682
1788
  @scope = @scope.parent
1683
1789
  end
1684
1790
 
@@ -1691,12 +1797,10 @@ module ActionDispatch
1691
1797
  options
1692
1798
  end
1693
1799
 
1694
- def nesting_depth #:nodoc:
1695
- @nesting.size
1696
- end
1697
-
1698
1800
  def shallow_nesting_depth #:nodoc:
1699
- @nesting.select(&:shallow?).size
1801
+ @scope.find_all { |node|
1802
+ node.frame[:scope_level_resource]
1803
+ }.count { |node| node.frame[:scope_level_resource].shallow? }
1700
1804
  end
1701
1805
 
1702
1806
  def param_constraint? #:nodoc:
@@ -1711,27 +1815,28 @@ module ActionDispatch
1711
1815
  resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
1712
1816
  end
1713
1817
 
1714
- def shallow_scope(path, options = {}) #:nodoc:
1818
+ def shallow_scope #:nodoc:
1715
1819
  scope = { :as => @scope[:shallow_prefix],
1716
1820
  :path => @scope[:shallow_path] }
1717
1821
  @scope = @scope.new scope
1718
1822
 
1719
- scope(path, options) { yield }
1823
+ yield
1720
1824
  ensure
1721
1825
  @scope = @scope.parent
1722
1826
  end
1723
1827
 
1724
1828
  def path_for_action(action, path) #:nodoc:
1725
- if path.blank? && canonical_action?(action)
1829
+ return "#{@scope[:path]}/#{path}" if path
1830
+
1831
+ if canonical_action?(action)
1726
1832
  @scope[:path].to_s
1727
1833
  else
1728
- "#{@scope[:path]}/#{action_path(action, path)}"
1834
+ "#{@scope[:path]}/#{action_path(action)}"
1729
1835
  end
1730
1836
  end
1731
1837
 
1732
- def action_path(name, path = nil) #:nodoc:
1733
- name = name.to_sym if name.is_a?(String)
1734
- path || @scope[:path_names][name] || name.to_s
1838
+ def action_path(name) #:nodoc:
1839
+ @scope[:path_names][name.to_sym] || name
1735
1840
  end
1736
1841
 
1737
1842
  def prefix_name_for_action(as, action) #:nodoc:
@@ -1757,14 +1862,15 @@ module ActionDispatch
1757
1862
  member_name = parent_resource.member_name
1758
1863
  end
1759
1864
 
1760
- name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
1865
+ action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
1866
+ candidate = action_name.select(&:present?).join('_')
1761
1867
 
1762
- if candidate = name.compact.join("_").presence
1868
+ unless candidate.empty?
1763
1869
  # If a name was not explicitly given, we check if it is valid
1764
1870
  # and return nil in case it isn't. Otherwise, we pass the invalid name
1765
1871
  # forward so the underlying router engine treats it and raises an exception.
1766
1872
  if as.nil?
1767
- candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
1873
+ candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
1768
1874
  else
1769
1875
  candidate
1770
1876
  end
@@ -1782,6 +1888,18 @@ module ActionDispatch
1782
1888
  delete :destroy if parent_resource.actions.include?(:destroy)
1783
1889
  end
1784
1890
  end
1891
+
1892
+ def api_only?
1893
+ @set.api_only?
1894
+ end
1895
+ private
1896
+
1897
+ def path_scope(path)
1898
+ @scope = @scope.new(path: merge_path_scope(@scope[:path], path))
1899
+ yield
1900
+ ensure
1901
+ @scope = @scope.parent
1902
+ end
1785
1903
  end
1786
1904
 
1787
1905
  # Routing Concerns allow you to declare common routes that can be reused
@@ -1892,14 +2010,14 @@ module ActionDispatch
1892
2010
  class Scope # :nodoc:
1893
2011
  OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
1894
2012
  :controller, :action, :path_names, :constraints,
1895
- :shallow, :blocks, :defaults, :options]
2013
+ :shallow, :blocks, :defaults, :via, :format, :options]
1896
2014
 
1897
2015
  RESOURCE_SCOPES = [:resource, :resources]
1898
2016
  RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
1899
2017
 
1900
2018
  attr_reader :parent, :scope_level
1901
2019
 
1902
- def initialize(hash, parent = {}, scope_level = nil)
2020
+ def initialize(hash, parent = NULL, scope_level = nil)
1903
2021
  @hash = hash
1904
2022
  @parent = parent
1905
2023
  @scope_level = scope_level
@@ -1947,27 +2065,34 @@ module ActionDispatch
1947
2065
  end
1948
2066
 
1949
2067
  def new_level(level)
1950
- self.class.new(self, self, level)
1951
- end
1952
-
1953
- def fetch(key, &block)
1954
- @hash.fetch(key, &block)
2068
+ self.class.new(frame, self, level)
1955
2069
  end
1956
2070
 
1957
2071
  def [](key)
1958
- @hash.fetch(key) { @parent[key] }
2072
+ scope = find { |node| node.frame.key? key }
2073
+ scope && scope.frame[key]
1959
2074
  end
1960
2075
 
1961
- def []=(k,v)
1962
- @hash[k] = v
2076
+ include Enumerable
2077
+
2078
+ def each
2079
+ node = self
2080
+ loop do
2081
+ break if node.equal? NULL
2082
+ yield node
2083
+ node = node.parent
2084
+ end
1963
2085
  end
2086
+
2087
+ def frame; @hash; end
2088
+
2089
+ NULL = Scope.new(nil, nil)
1964
2090
  end
1965
2091
 
1966
2092
  def initialize(set) #:nodoc:
1967
2093
  @set = set
1968
2094
  @scope = Scope.new({ :path_names => @set.resources_path_names })
1969
2095
  @concerns = {}
1970
- @nesting = []
1971
2096
  end
1972
2097
 
1973
2098
  include Base