actionpack 4.2.11.3 → 5.0.7.2

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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +890 -384
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/base.rb +28 -38
  6. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
  7. data/lib/abstract_controller/caching.rb +62 -0
  8. data/lib/abstract_controller/callbacks.rb +54 -19
  9. data/lib/abstract_controller/collector.rb +4 -9
  10. data/lib/abstract_controller/error.rb +4 -0
  11. data/lib/abstract_controller/helpers.rb +4 -3
  12. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  13. data/lib/abstract_controller/rendering.rb +28 -18
  14. data/lib/abstract_controller/translation.rb +8 -7
  15. data/lib/abstract_controller.rb +6 -2
  16. data/lib/action_controller/api/api_rendering.rb +14 -0
  17. data/lib/action_controller/api.rb +147 -0
  18. data/lib/action_controller/base.rb +14 -11
  19. data/lib/action_controller/caching.rb +13 -58
  20. data/lib/action_controller/form_builder.rb +48 -0
  21. data/lib/action_controller/log_subscriber.rb +3 -10
  22. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  23. data/lib/action_controller/metal/conditional_get.rb +106 -34
  24. data/lib/action_controller/metal/cookies.rb +1 -3
  25. data/lib/action_controller/metal/data_streaming.rb +14 -34
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +8 -2
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +11 -11
  29. data/lib/action_controller/metal/head.rb +14 -8
  30. data/lib/action_controller/metal/helpers.rb +15 -6
  31. data/lib/action_controller/metal/http_authentication.rb +44 -35
  32. data/lib/action_controller/metal/implicit_render.rb +61 -6
  33. data/lib/action_controller/metal/instrumentation.rb +5 -5
  34. data/lib/action_controller/metal/live.rb +71 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +9 -9
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +83 -40
  39. data/lib/action_controller/metal/rendering.rb +38 -6
  40. data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
  41. data/lib/action_controller/metal/rescue.rb +3 -12
  42. data/lib/action_controller/metal/streaming.rb +4 -4
  43. data/lib/action_controller/metal/strong_parameters.rb +527 -134
  44. data/lib/action_controller/metal/testing.rb +1 -12
  45. data/lib/action_controller/metal/url_for.rb +12 -5
  46. data/lib/action_controller/metal.rb +88 -63
  47. data/lib/action_controller/railtie.rb +11 -7
  48. data/lib/action_controller/renderer.rb +113 -0
  49. data/lib/action_controller/template_assertions.rb +9 -0
  50. data/lib/action_controller/test_case.rb +311 -374
  51. data/lib/action_controller.rb +12 -9
  52. data/lib/action_dispatch/http/cache.rb +73 -34
  53. data/lib/action_dispatch/http/filter_parameters.rb +16 -12
  54. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  55. data/lib/action_dispatch/http/headers.rb +45 -14
  56. data/lib/action_dispatch/http/mime_negotiation.rb +42 -23
  57. data/lib/action_dispatch/http/mime_type.rb +126 -90
  58. data/lib/action_dispatch/http/mime_types.rb +3 -4
  59. data/lib/action_dispatch/http/parameter_filter.rb +19 -9
  60. data/lib/action_dispatch/http/parameters.rb +70 -40
  61. data/lib/action_dispatch/http/request.rb +144 -89
  62. data/lib/action_dispatch/http/response.rb +215 -102
  63. data/lib/action_dispatch/http/upload.rb +6 -2
  64. data/lib/action_dispatch/http/url.rb +117 -8
  65. data/lib/action_dispatch/journey/formatter.rb +47 -30
  66. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  67. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  68. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  69. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  70. data/lib/action_dispatch/journey/parser.rb +2 -0
  71. data/lib/action_dispatch/journey/parser_extras.rb +8 -2
  72. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  73. data/lib/action_dispatch/journey/route.rb +88 -26
  74. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  75. data/lib/action_dispatch/journey/router.rb +8 -10
  76. data/lib/action_dispatch/journey/routes.rb +14 -15
  77. data/lib/action_dispatch/journey/visitors.rb +89 -44
  78. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  79. data/lib/action_dispatch/middleware/cookies.rb +188 -134
  80. data/lib/action_dispatch/middleware/debug_exceptions.rb +128 -49
  81. data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
  82. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  83. data/lib/action_dispatch/middleware/executor.rb +19 -0
  84. data/lib/action_dispatch/middleware/flash.rb +66 -45
  85. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  86. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  87. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  88. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  89. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  90. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  91. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  92. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  93. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  94. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  95. data/lib/action_dispatch/middleware/ssl.rb +124 -36
  96. data/lib/action_dispatch/middleware/stack.rb +44 -40
  97. data/lib/action_dispatch/middleware/static.rb +51 -35
  98. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  99. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  101. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  103. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  104. data/lib/action_dispatch/railtie.rb +2 -2
  105. data/lib/action_dispatch/request/session.rb +69 -33
  106. data/lib/action_dispatch/request/utils.rb +51 -19
  107. data/lib/action_dispatch/routing/inspector.rb +32 -43
  108. data/lib/action_dispatch/routing/mapper.rb +515 -348
  109. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  110. data/lib/action_dispatch/routing/redirection.rb +5 -4
  111. data/lib/action_dispatch/routing/route_set.rb +148 -240
  112. data/lib/action_dispatch/routing/url_for.rb +27 -10
  113. data/lib/action_dispatch/routing.rb +17 -13
  114. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  115. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  116. data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
  117. data/lib/action_dispatch/testing/assertions.rb +1 -1
  118. data/lib/action_dispatch/testing/integration.rb +377 -149
  119. data/lib/action_dispatch/testing/request_encoder.rb +53 -0
  120. data/lib/action_dispatch/testing/test_process.rb +24 -20
  121. data/lib/action_dispatch/testing/test_request.rb +22 -31
  122. data/lib/action_dispatch/testing/test_response.rb +12 -4
  123. data/lib/action_dispatch.rb +4 -1
  124. data/lib/action_pack/gem_version.rb +4 -4
  125. data/lib/action_pack.rb +1 -1
  126. metadata +32 -34
  127. data/lib/action_controller/metal/hide_actions.rb +0 -40
  128. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  129. data/lib/action_controller/middleware.rb +0 -39
  130. data/lib/action_controller/model_naming.rb +0 -12
  131. data/lib/action_dispatch/journey/backwards.rb +0 -5
  132. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  133. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  134. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  135. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  136. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,5 +1,3 @@
1
- require 'action_dispatch/journey/router/strexp'
2
-
3
1
  module ActionDispatch
4
2
  module Journey # :nodoc:
5
3
  module Path # :nodoc:
@@ -7,14 +5,20 @@ module ActionDispatch
7
5
  attr_reader :spec, :requirements, :anchored
8
6
 
9
7
  def self.from_string string
10
- new Journey::Router::Strexp.build(string, {}, ["/.?"], true)
8
+ build(string, {}, "/.?", true)
9
+ end
10
+
11
+ def self.build(path, requirements, separators, anchored)
12
+ parser = Journey::Parser.new
13
+ ast = parser.parse path
14
+ new ast, requirements, separators, anchored
11
15
  end
12
16
 
13
- def initialize(strexp)
14
- @spec = strexp.ast
15
- @requirements = strexp.requirements
16
- @separators = strexp.separators.join
17
- @anchored = strexp.anchor
17
+ def initialize(ast, requirements, separators, anchored)
18
+ @spec = ast
19
+ @requirements = requirements
20
+ @separators = separators
21
+ @anchored = anchored
18
22
 
19
23
  @names = nil
20
24
  @optional_names = nil
@@ -28,12 +32,12 @@ module ActionDispatch
28
32
  end
29
33
 
30
34
  def ast
31
- @spec.grep(Nodes::Symbol).each do |node|
35
+ @spec.find_all(&:symbol?).each do |node|
32
36
  re = @requirements[node.to_sym]
33
37
  node.regexp = re if re
34
38
  end
35
39
 
36
- @spec.grep(Nodes::Star).each do |node|
40
+ @spec.find_all(&:star?).each do |node|
37
41
  node = node.left
38
42
  node.regexp = @requirements[node.to_sym] || /(.+)/
39
43
  end
@@ -42,7 +46,7 @@ module ActionDispatch
42
46
  end
43
47
 
44
48
  def names
45
- @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
49
+ @names ||= spec.find_all(&:symbol?).map(&:name)
46
50
  end
47
51
 
48
52
  def required_names
@@ -50,34 +54,9 @@ module ActionDispatch
50
54
  end
51
55
 
52
56
  def optional_names
53
- @optional_names ||= spec.grep(Nodes::Group).flat_map { |group|
54
- group.grep(Nodes::Symbol)
55
- }.map { |n| n.name }.uniq
56
- end
57
-
58
- class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
59
- attr_reader :offsets
60
-
61
- def initialize(matchers)
62
- @matchers = matchers
63
- @capture_count = [0]
64
- end
65
-
66
- def visit(node)
67
- super
68
- @capture_count
69
- end
70
-
71
- def visit_SYMBOL(node)
72
- node = node.to_sym
73
-
74
- if @matchers.key?(node)
75
- re = /#{@matchers[node]}|/
76
- @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
77
- else
78
- @capture_count << (@capture_count.last || 0)
79
- end
80
- end
57
+ @optional_names ||= spec.find_all(&:group?).flat_map { |group|
58
+ group.find_all(&:symbol?)
59
+ }.map(&:name).uniq
81
60
  end
82
61
 
83
62
  class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
@@ -122,6 +101,11 @@ module ActionDispatch
122
101
  re = @matchers[node.left.to_sym] || '.+'
123
102
  "(#{re})"
124
103
  end
104
+
105
+ def visit_OR(node)
106
+ children = node.children.map { |n| visit n }
107
+ "(?:#{children.join(?|)})"
108
+ end
125
109
  end
126
110
 
127
111
  class UnanchoredRegexp < AnchoredRegexp # :nodoc:
@@ -140,7 +124,7 @@ module ActionDispatch
140
124
  end
141
125
 
142
126
  def captures
143
- (length - 1).times.map { |i| self[i + 1] }
127
+ Array.new(length - 1) { |i| self[i + 1] }
144
128
  end
145
129
 
146
130
  def [](x)
@@ -184,8 +168,20 @@ module ActionDispatch
184
168
  def offsets
185
169
  return @offsets if @offsets
186
170
 
187
- viz = RegexpOffsets.new(@requirements)
188
- @offsets = viz.accept(spec)
171
+ @offsets = [0]
172
+
173
+ spec.find_all(&:symbol?).each do |node|
174
+ node = node.to_sym
175
+
176
+ if @requirements.key?(node)
177
+ re = /#{@requirements[node]}|/
178
+ @offsets.push((re.match('').length - 1) + @offsets.last)
179
+ else
180
+ @offsets << @offsets.last
181
+ end
182
+ end
183
+
184
+ @offsets
189
185
  end
190
186
  end
191
187
  end
@@ -1,41 +1,89 @@
1
1
  module ActionDispatch
2
- module Journey # :nodoc:
3
- class Route # :nodoc:
4
- attr_reader :app, :path, :defaults, :name
2
+ # :stopdoc:
3
+ module Journey
4
+ class Route
5
+ attr_reader :app, :path, :defaults, :name, :precedence
5
6
 
6
- attr_reader :constraints
7
+ attr_reader :constraints, :internal
7
8
  alias :conditions :constraints
8
9
 
9
- attr_accessor :precedence
10
+ module VerbMatchers
11
+ VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
12
+ VERBS.each do |v|
13
+ class_eval <<-eoc
14
+ class #{v}
15
+ def self.verb; name.split("::").last; end
16
+ def self.call(req); req.#{v.downcase}?; end
17
+ end
18
+ eoc
19
+ end
20
+
21
+ class Unknown
22
+ attr_reader :verb
23
+
24
+ def initialize(verb)
25
+ @verb = verb
26
+ end
27
+
28
+ def call(request); @verb === request.request_method; end
29
+ end
30
+
31
+ class All
32
+ def self.call(_); true; end
33
+ def self.verb; ''; end
34
+ end
35
+
36
+ VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash|
37
+ klass = const_get verb
38
+ hash[verb] = klass
39
+ hash[verb.downcase] = klass
40
+ hash[verb.downcase.to_sym] = klass
41
+ end
42
+
43
+ end
44
+
45
+ def self.verb_matcher(verb)
46
+ VerbMatchers::VERB_TO_CLASS.fetch(verb) do
47
+ VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
48
+ end
49
+ end
50
+
51
+ def self.build(name, app, path, constraints, required_defaults, defaults)
52
+ request_method_match = verb_matcher(constraints.delete(:request_method))
53
+ new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
54
+ end
10
55
 
11
56
  ##
12
57
  # +path+ is a path constraint.
13
58
  # +constraints+ is a hash of constraints to be applied to this route.
14
- def initialize(name, app, path, constraints, defaults = {})
59
+ def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false)
15
60
  @name = name
16
61
  @app = app
17
62
  @path = path
18
63
 
64
+ @request_method_match = request_method_match
19
65
  @constraints = constraints
20
66
  @defaults = defaults
21
67
  @required_defaults = nil
68
+ @_required_defaults = required_defaults
22
69
  @required_parts = nil
23
70
  @parts = nil
24
71
  @decorated_ast = nil
25
- @precedence = 0
72
+ @precedence = precedence
26
73
  @path_formatter = @path.build_formatter
74
+ @internal = internal
27
75
  end
28
76
 
29
77
  def ast
30
78
  @decorated_ast ||= begin
31
79
  decorated_ast = path.ast
32
- decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
80
+ decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
33
81
  decorated_ast
34
82
  end
35
83
  end
36
84
 
37
- def requirements # :nodoc:
38
- # needed for rails `rake routes`
85
+ def requirements
86
+ # needed for rails `rails routes`
39
87
  @defaults.merge(path.requirements).delete_if { |_,v|
40
88
  /.+?/ == v
41
89
  }
@@ -49,18 +97,23 @@ module ActionDispatch
49
97
  required_parts + required_defaults.keys
50
98
  end
51
99
 
52
- def score(constraints)
100
+ def score(supplied_keys)
53
101
  required_keys = path.required_names
54
- supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
55
102
 
56
- return -1 unless (required_keys - supplied_keys).empty?
103
+ required_keys.each do |k|
104
+ return -1 unless supplied_keys.include?(k)
105
+ end
106
+
107
+ score = 0
108
+ path.names.each do |k|
109
+ score += 1 if supplied_keys.include?(k)
110
+ end
57
111
 
58
- score = (supplied_keys & path.names).length
59
112
  score + (required_defaults.length * 2)
60
113
  end
61
114
 
62
115
  def parts
63
- @parts ||= segments.map { |n| n.to_sym }
116
+ @parts ||= segments.map(&:to_sym)
64
117
  end
65
118
  alias :segment_keys :parts
66
119
 
@@ -68,16 +121,12 @@ module ActionDispatch
68
121
  @path_formatter.evaluate path_options
69
122
  end
70
123
 
71
- def optional_parts
72
- path.optional_names.map { |n| n.to_sym }
73
- end
74
-
75
124
  def required_parts
76
- @required_parts ||= path.required_names.map { |n| n.to_sym }
125
+ @required_parts ||= path.required_names.map(&:to_sym)
77
126
  end
78
127
 
79
128
  def required_default?(key)
80
- (constraints[:required_defaults] || []).include?(key)
129
+ @_required_defaults.include?(key)
81
130
  end
82
131
 
83
132
  def required_defaults
@@ -95,9 +144,8 @@ module ActionDispatch
95
144
  end
96
145
 
97
146
  def matches?(request)
98
- constraints.all? do |method, value|
99
- next true unless request.respond_to?(method)
100
-
147
+ match_verb(request) &&
148
+ constraints.all? { |method, value|
101
149
  case value
102
150
  when Regexp, String
103
151
  value === request.send(method).to_s
@@ -110,16 +158,30 @@ module ActionDispatch
110
158
  else
111
159
  value === request.send(method)
112
160
  end
113
- end
161
+ }
114
162
  end
115
163
 
116
164
  def ip
117
165
  constraints[:ip] || //
118
166
  end
119
167
 
168
+ def requires_matching_verb?
169
+ !@request_method_match.all? { |x| x == VerbMatchers::All }
170
+ end
171
+
120
172
  def verb
121
- constraints[:request_method] || //
173
+ verbs.join('|')
174
+ end
175
+
176
+ private
177
+ def verbs
178
+ @request_method_match.map(&:verb)
179
+ end
180
+
181
+ def match_verb(request)
182
+ @request_method_match.any? { |m| m.call request }
122
183
  end
123
184
  end
124
185
  end
186
+ # :startdoc:
125
187
  end
@@ -13,11 +13,11 @@ module ActionDispatch
13
13
  # normalize_path("") # => "/"
14
14
  # normalize_path("/%ab") # => "/%AB"
15
15
  def self.normalize_path(path)
16
- path = "/#{path}".force_encoding(Encoding::UTF_8)
17
- path.squeeze!('/')
18
- path.sub!(%r{/+\Z}, '')
16
+ path = "/#{path}"
17
+ path.squeeze!('/'.freeze)
18
+ path.sub!(%r{/+\Z}, ''.freeze)
19
19
  path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
20
- path = '/' if path == ''
20
+ path = '/' if path == ''.freeze
21
21
  path
22
22
  end
23
23
 
@@ -55,7 +55,7 @@ module ActionDispatch
55
55
 
56
56
  def unescape_uri(uri)
57
57
  encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding
58
- uri.gsub(ESCAPED) { [$&[1, 2].hex].pack('C') }.force_encoding(encoding)
58
+ uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack('C') }.force_encoding(encoding)
59
59
  end
60
60
 
61
61
  protected
@@ -1,5 +1,4 @@
1
1
  require 'action_dispatch/journey/router/utils'
2
- require 'action_dispatch/journey/router/strexp'
3
2
  require 'action_dispatch/journey/routes'
4
3
  require 'action_dispatch/journey/formatter'
5
4
 
@@ -17,9 +16,6 @@ module ActionDispatch
17
16
  class RoutingError < ::StandardError # :nodoc:
18
17
  end
19
18
 
20
- # :nodoc:
21
- VERSION = '2.0.0'
22
-
23
19
  attr_accessor :routes
24
20
 
25
21
  def initialize(routes)
@@ -68,15 +64,17 @@ module ActionDispatch
68
64
 
69
65
  def visualizer
70
66
  tt = GTG::Builder.new(ast).transition_table
71
- groups = partitioned_routes.first.map(&:ast).group_by { |a| a.to_s }
72
- asts = groups.values.map { |v| v.first }
67
+ groups = partitioned_routes.first.map(&:ast).group_by(&:to_s)
68
+ asts = groups.values.map(&:first)
73
69
  tt.visualizer(asts)
74
70
  end
75
71
 
76
72
  private
77
73
 
78
74
  def partitioned_routes
79
- routes.partitioned_routes
75
+ routes.partition { |r|
76
+ r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? }
77
+ }
80
78
  end
81
79
 
82
80
  def ast
@@ -88,7 +86,7 @@ module ActionDispatch
88
86
  end
89
87
 
90
88
  def custom_routes
91
- partitioned_routes.last
89
+ routes.custom_routes
92
90
  end
93
91
 
94
92
  def filter_routes(path)
@@ -102,7 +100,7 @@ module ActionDispatch
102
100
  }
103
101
 
104
102
  routes =
105
- if req.request_method == "HEAD"
103
+ if req.head?
106
104
  match_head_routes(routes, req)
107
105
  else
108
106
  match_routes(routes, req)
@@ -121,7 +119,7 @@ module ActionDispatch
121
119
  end
122
120
 
123
121
  def match_head_routes(routes, req)
124
- verb_specific_routes = routes.reject { |route| route.verb == // }
122
+ verb_specific_routes = routes.select(&:requires_matching_verb?)
125
123
  head_routes = match_routes(verb_specific_routes, req)
126
124
 
127
125
  if head_routes.empty?
@@ -5,13 +5,13 @@ module ActionDispatch
5
5
  class Routes # :nodoc:
6
6
  include Enumerable
7
7
 
8
- attr_reader :routes, :named_routes
8
+ attr_reader :routes, :custom_routes, :anchored_routes
9
9
 
10
10
  def initialize
11
11
  @routes = []
12
- @named_routes = {}
13
12
  @ast = nil
14
- @partitioned_routes = nil
13
+ @anchored_routes = []
14
+ @custom_routes = []
15
15
  @simulator = nil
16
16
  end
17
17
 
@@ -34,18 +34,21 @@ module ActionDispatch
34
34
 
35
35
  def clear
36
36
  routes.clear
37
- named_routes.clear
37
+ anchored_routes.clear
38
+ custom_routes.clear
38
39
  end
39
40
 
40
- def partitioned_routes
41
- @partitioned_routes ||= routes.partition do |r|
42
- r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
41
+ def partition_route(route)
42
+ if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?)
43
+ anchored_routes << route
44
+ else
45
+ custom_routes << route
43
46
  end
44
47
  end
45
48
 
46
49
  def ast
47
50
  @ast ||= begin
48
- asts = partitioned_routes.first.map(&:ast)
51
+ asts = anchored_routes.map(&:ast)
49
52
  Nodes::Or.new(asts) unless asts.empty?
50
53
  end
51
54
  end
@@ -57,13 +60,10 @@ module ActionDispatch
57
60
  end
58
61
  end
59
62
 
60
- # Add a route to the routing table.
61
- def add_route(app, path, conditions, defaults, name = nil)
62
- route = Route.new(name, app, path, conditions, defaults)
63
-
64
- route.precedence = routes.length
63
+ def add_route(name, mapping)
64
+ route = mapping.make_route name, routes.length
65
65
  routes << route
66
- named_routes[name] = route if name && !named_routes[name]
66
+ partition_route(route)
67
67
  clear_cache!
68
68
  route
69
69
  end
@@ -72,7 +72,6 @@ module ActionDispatch
72
72
 
73
73
  def clear_cache!
74
74
  @ast = nil
75
- @partitioned_routes = nil
76
75
  @simulator = nil
77
76
  end
78
77
  end
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  module ActionDispatch
4
- module Journey # :nodoc:
2
+ # :stopdoc:
3
+ module Journey
5
4
  class Format
6
5
  ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) }
7
6
  ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) }
@@ -92,6 +91,45 @@ module ActionDispatch
92
91
  end
93
92
  end
94
93
 
94
+ class FunctionalVisitor # :nodoc:
95
+ DISPATCH_CACHE = {}
96
+
97
+ def accept(node, seed)
98
+ visit(node, seed)
99
+ end
100
+
101
+ def visit node, seed
102
+ send(DISPATCH_CACHE[node.type], node, seed)
103
+ end
104
+
105
+ def binary(node, seed)
106
+ visit(node.right, visit(node.left, seed))
107
+ end
108
+ def visit_CAT(n, seed); binary(n, seed); end
109
+
110
+ def nary(node, seed)
111
+ node.children.inject(seed) { |s, c| visit(c, s) }
112
+ end
113
+ def visit_OR(n, seed); nary(n, seed); end
114
+
115
+ def unary(node, seed)
116
+ visit(node.left, seed)
117
+ end
118
+ def visit_GROUP(n, seed); unary(n, seed); end
119
+ def visit_STAR(n, seed); unary(n, seed); end
120
+
121
+ def terminal(node, seed); seed; end
122
+ def visit_LITERAL(n, seed); terminal(n, seed); end
123
+ def visit_SYMBOL(n, seed); terminal(n, seed); end
124
+ def visit_SLASH(n, seed); terminal(n, seed); end
125
+ def visit_DOT(n, seed); terminal(n, seed); end
126
+
127
+ instance_methods(false).each do |pim|
128
+ next unless pim =~ /^visit_(.*)$/
129
+ DISPATCH_CACHE[$1.to_sym] = pim
130
+ end
131
+ end
132
+
95
133
  class FormatBuilder < Visitor # :nodoc:
96
134
  def accept(node); Journey::Format.new(super); end
97
135
  def terminal(node); [node.left]; end
@@ -117,105 +155,112 @@ module ActionDispatch
117
155
  end
118
156
 
119
157
  # Loop through the requirements AST
120
- class Each < Visitor # :nodoc:
121
- attr_reader :block
122
-
123
- def initialize(block)
124
- @block = block
125
- end
126
-
127
- def visit(node)
158
+ class Each < FunctionalVisitor # :nodoc:
159
+ def visit(node, block)
128
160
  block.call(node)
129
161
  super
130
162
  end
163
+
164
+ INSTANCE = new
131
165
  end
132
166
 
133
- class String < Visitor # :nodoc:
167
+ class String < FunctionalVisitor # :nodoc:
134
168
  private
135
169
 
136
- def binary(node)
137
- [visit(node.left), visit(node.right)].join
170
+ def binary(node, seed)
171
+ visit(node.right, visit(node.left, seed))
138
172
  end
139
173
 
140
- def nary(node)
141
- node.children.map { |c| visit(c) }.join '|'
174
+ def nary(node, seed)
175
+ last_child = node.children.last
176
+ node.children.inject(seed) { |s, c|
177
+ string = visit(c, s)
178
+ string << "|".freeze unless last_child == c
179
+ string
180
+ }
142
181
  end
143
182
 
144
- def terminal(node)
145
- node.left
183
+ def terminal(node, seed)
184
+ seed + node.left
146
185
  end
147
186
 
148
- def visit_GROUP(node)
149
- "(#{visit(node.left)})"
187
+ def visit_GROUP(node, seed)
188
+ visit(node.left, seed << "(".freeze) << ")".freeze
150
189
  end
190
+
191
+ INSTANCE = new
151
192
  end
152
193
 
153
- class Dot < Visitor # :nodoc:
194
+ class Dot < FunctionalVisitor # :nodoc:
154
195
  def initialize
155
196
  @nodes = []
156
197
  @edges = []
157
198
  end
158
199
 
159
- def accept(node)
200
+ def accept(node, seed = [[], []])
160
201
  super
202
+ nodes, edges = seed
161
203
  <<-eodot
162
204
  digraph parse_tree {
163
205
  size="8,5"
164
206
  node [shape = none];
165
207
  edge [dir = none];
166
- #{@nodes.join "\n"}
167
- #{@edges.join("\n")}
208
+ #{nodes.join "\n"}
209
+ #{edges.join("\n")}
168
210
  }
169
211
  eodot
170
212
  end
171
213
 
172
214
  private
173
215
 
174
- def binary(node)
175
- node.children.each do |c|
176
- @edges << "#{node.object_id} -> #{c.object_id};"
177
- end
216
+ def binary(node, seed)
217
+ seed.last.concat node.children.map { |c|
218
+ "#{node.object_id} -> #{c.object_id};"
219
+ }
178
220
  super
179
221
  end
180
222
 
181
- def nary(node)
182
- node.children.each do |c|
183
- @edges << "#{node.object_id} -> #{c.object_id};"
184
- end
223
+ def nary(node, seed)
224
+ seed.last.concat node.children.map { |c|
225
+ "#{node.object_id} -> #{c.object_id};"
226
+ }
185
227
  super
186
228
  end
187
229
 
188
- def unary(node)
189
- @edges << "#{node.object_id} -> #{node.left.object_id};"
230
+ def unary(node, seed)
231
+ seed.last << "#{node.object_id} -> #{node.left.object_id};"
190
232
  super
191
233
  end
192
234
 
193
- def visit_GROUP(node)
194
- @nodes << "#{node.object_id} [label=\"()\"];"
235
+ def visit_GROUP(node, seed)
236
+ seed.first << "#{node.object_id} [label=\"()\"];"
195
237
  super
196
238
  end
197
239
 
198
- def visit_CAT(node)
199
- @nodes << "#{node.object_id} [label=\"○\"];"
240
+ def visit_CAT(node, seed)
241
+ seed.first << "#{node.object_id} [label=\"○\"];"
200
242
  super
201
243
  end
202
244
 
203
- def visit_STAR(node)
204
- @nodes << "#{node.object_id} [label=\"*\"];"
245
+ def visit_STAR(node, seed)
246
+ seed.first << "#{node.object_id} [label=\"*\"];"
205
247
  super
206
248
  end
207
249
 
208
- def visit_OR(node)
209
- @nodes << "#{node.object_id} [label=\"|\"];"
250
+ def visit_OR(node, seed)
251
+ seed.first << "#{node.object_id} [label=\"|\"];"
210
252
  super
211
253
  end
212
254
 
213
- def terminal(node)
255
+ def terminal(node, seed)
214
256
  value = node.left
215
257
 
216
- @nodes << "#{node.object_id} [label=\"#{value}\"];"
258
+ seed.first << "#{node.object_id} [label=\"#{value}\"];"
259
+ seed
217
260
  end
261
+ INSTANCE = new
218
262
  end
219
263
  end
220
264
  end
265
+ # :startdoc:
221
266
  end