actionpack 4.2.10 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +553 -401
  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 +52 -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 +10 -13
  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 +11 -32
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +10 -10
  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 +66 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +85 -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 +293 -90
  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/renderer.rb +111 -0
  48. data/lib/action_controller/template_assertions.rb +9 -0
  49. data/lib/action_controller/test_case.rb +288 -368
  50. data/lib/action_controller.rb +12 -9
  51. data/lib/action_dispatch/http/cache.rb +73 -34
  52. data/lib/action_dispatch/http/filter_parameters.rb +15 -11
  53. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  54. data/lib/action_dispatch/http/headers.rb +44 -13
  55. data/lib/action_dispatch/http/mime_negotiation.rb +41 -23
  56. data/lib/action_dispatch/http/mime_type.rb +126 -90
  57. data/lib/action_dispatch/http/mime_types.rb +3 -4
  58. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  59. data/lib/action_dispatch/http/parameters.rb +54 -41
  60. data/lib/action_dispatch/http/request.rb +149 -82
  61. data/lib/action_dispatch/http/response.rb +206 -102
  62. data/lib/action_dispatch/http/url.rb +117 -8
  63. data/lib/action_dispatch/journey/formatter.rb +39 -28
  64. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  65. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  66. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  67. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  68. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  69. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  70. data/lib/action_dispatch/journey/route.rb +74 -19
  71. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  72. data/lib/action_dispatch/journey/router.rb +5 -9
  73. data/lib/action_dispatch/journey/routes.rb +14 -15
  74. data/lib/action_dispatch/journey/visitors.rb +86 -43
  75. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  76. data/lib/action_dispatch/middleware/cookies.rb +189 -135
  77. data/lib/action_dispatch/middleware/debug_exceptions.rb +124 -49
  78. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  79. data/lib/action_dispatch/middleware/executor.rb +19 -0
  80. data/lib/action_dispatch/middleware/flash.rb +66 -45
  81. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  82. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  83. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  84. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  85. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  91. data/lib/action_dispatch/middleware/ssl.rb +115 -36
  92. data/lib/action_dispatch/middleware/stack.rb +44 -40
  93. data/lib/action_dispatch/middleware/static.rb +51 -35
  94. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  95. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  96. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  98. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  99. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  100. data/lib/action_dispatch/railtie.rb +2 -2
  101. data/lib/action_dispatch/request/session.rb +69 -33
  102. data/lib/action_dispatch/request/utils.rb +51 -19
  103. data/lib/action_dispatch/routing/inspector.rb +32 -43
  104. data/lib/action_dispatch/routing/mapper.rb +491 -338
  105. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  106. data/lib/action_dispatch/routing/redirection.rb +3 -3
  107. data/lib/action_dispatch/routing/route_set.rb +145 -238
  108. data/lib/action_dispatch/routing/url_for.rb +27 -10
  109. data/lib/action_dispatch/routing.rb +17 -13
  110. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  111. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  112. data/lib/action_dispatch/testing/assertions/routing.rb +11 -10
  113. data/lib/action_dispatch/testing/assertions.rb +1 -1
  114. data/lib/action_dispatch/testing/integration.rb +368 -97
  115. data/lib/action_dispatch/testing/test_process.rb +5 -6
  116. data/lib/action_dispatch/testing/test_request.rb +22 -31
  117. data/lib/action_dispatch/testing/test_response.rb +7 -4
  118. data/lib/action_dispatch.rb +3 -1
  119. data/lib/action_pack/gem_version.rb +3 -3
  120. data/lib/action_pack.rb +1 -1
  121. metadata +30 -34
  122. data/lib/action_controller/metal/hide_actions.rb +0 -40
  123. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  124. data/lib/action_controller/middleware.rb +0 -39
  125. data/lib/action_controller/model_naming.rb +0 -12
  126. data/lib/action_dispatch/journey/backwards.rb +0 -5
  127. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  128. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  129. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  130. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  131. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,5 +1,4 @@
1
1
  require 'action_controller/metal/exceptions'
2
- require 'active_support/deprecation'
3
2
 
4
3
  module ActionDispatch
5
4
  module Journey
@@ -15,7 +14,7 @@ module ActionDispatch
15
14
 
16
15
  def generate(name, options, path_parameters, parameterize = nil)
17
16
  constraints = path_parameters.merge(options)
18
- missing_keys = []
17
+ missing_keys = nil # need for variable scope
19
18
 
20
19
  match_route(name, constraints) do |route|
21
20
  parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize)
@@ -26,22 +25,27 @@ module ActionDispatch
26
25
  next unless name || route.dispatcher?
27
26
 
28
27
  missing_keys = missing_keys(route, parameterized_parts)
29
- next unless missing_keys.empty?
28
+ next if missing_keys && !missing_keys.empty?
30
29
  params = options.dup.delete_if do |key, _|
31
30
  parameterized_parts.key?(key) || route.defaults.key?(key)
32
31
  end
33
32
 
34
33
  defaults = route.defaults
35
34
  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)
35
+
36
+ route.parts.reverse_each do |key|
37
+ break if defaults[key].nil? && parameterized_parts[key].present?
38
+ break if parameterized_parts[key].to_s != defaults[key].to_s
39
+ break if required_parts.include?(key)
40
+
41
+ parameterized_parts.delete(key)
38
42
  end
39
43
 
40
44
  return [route.format(parameterized_parts), params]
41
45
  end
42
46
 
43
47
  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?
48
+ message << " missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty?
45
49
 
46
50
  raise ActionController::UrlGenerationError, message
47
51
  end
@@ -55,12 +59,12 @@ module ActionDispatch
55
59
  def extract_parameterized_parts(route, options, recall, parameterize = nil)
56
60
  parameterized_parts = recall.merge(options)
57
61
 
58
- keys_to_keep = route.parts.reverse.drop_while { |part|
62
+ keys_to_keep = route.parts.reverse_each.drop_while { |part|
59
63
  !options.key?(part) || (options[part] || recall[part]).nil?
60
64
  } | route.required_parts
61
65
 
62
- (parameterized_parts.keys - keys_to_keep).each do |bad_key|
63
- parameterized_parts.delete(bad_key)
66
+ parameterized_parts.delete_if do |bad_key, _|
67
+ !keys_to_keep.include?(bad_key)
64
68
  end
65
69
 
66
70
  if parameterize
@@ -81,9 +85,6 @@ module ActionDispatch
81
85
  if named_routes.key?(name)
82
86
  yield named_routes[name]
83
87
  else
84
- # Make sure we don't show the deprecation warning more than once
85
- warned = false
86
-
87
88
  routes = non_recursive(cache, options)
88
89
 
89
90
  hash = routes.group_by { |_, r| r.score(options) }
@@ -92,17 +93,6 @@ module ActionDispatch
92
93
  break if score < 0
93
94
 
94
95
  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
-
106
96
  yield route
107
97
  end
108
98
  end
@@ -125,15 +115,36 @@ module ActionDispatch
125
115
  routes
126
116
  end
127
117
 
118
+ module RegexCaseComparator
119
+ DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/
120
+ DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/
121
+
122
+ def self.===(regex)
123
+ DEFAULT_INPUT == regex
124
+ end
125
+ end
126
+
128
127
  # Returns an array populated with missing keys if any are present.
129
128
  def missing_keys(route, parts)
130
- missing_keys = []
129
+ missing_keys = nil
131
130
  tests = route.path.requirements
132
131
  route.required_parts.each { |key|
133
- if tests.key?(key)
134
- missing_keys << key unless /\A#{tests[key]}\Z/ === parts[key]
132
+ case tests[key]
133
+ when nil
134
+ unless parts[key]
135
+ missing_keys ||= []
136
+ missing_keys << key
137
+ end
138
+ when RegexCaseComparator
139
+ unless RegexCaseComparator::DEFAULT_REGEX === parts[key]
140
+ missing_keys ||= []
141
+ missing_keys << key
142
+ end
135
143
  else
136
- missing_keys << key unless parts[key]
144
+ unless /\A#{tests[key]}\Z/ === parts[key]
145
+ missing_keys ||= []
146
+ missing_keys << key
147
+ end
137
148
  end
138
149
  }
139
150
  missing_keys
@@ -149,7 +160,7 @@ module ActionDispatch
149
160
 
150
161
  def build_cache
151
162
  root = { ___routes: [] }
152
- routes.each_with_index do |route, i|
163
+ routes.routes.each_with_index do |route, i|
153
164
  leaf = route.required_defaults.inject(root) do |h, tuple|
154
165
  h[tuple] ||= {}
155
166
  end
@@ -109,7 +109,7 @@ module ActionDispatch
109
109
  svg = to_svg
110
110
  javascripts = [states, fsm_js]
111
111
 
112
- # Annoying hack for 1.9 warnings
112
+ # Annoying hack warnings
113
113
  fun_routes = fun_routes
114
114
  stylesheets = stylesheets
115
115
  svg = svg
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module ActionDispatch
4
2
  module Journey # :nodoc:
5
3
  module NFA # :nodoc:
@@ -45,51 +45,6 @@ module ActionDispatch
45
45
  (@table.keys + @table.values.flat_map(&:keys)).uniq
46
46
  end
47
47
 
48
- # Returns a generalized transition graph with reduced states. The states
49
- # are reduced like a DFA, but the table must be simulated like an NFA.
50
- #
51
- # Edges of the GTG are regular expressions.
52
- def generalized_table
53
- gt = GTG::TransitionTable.new
54
- marked = {}
55
- state_id = Hash.new { |h,k| h[k] = h.length }
56
- alphabet = self.alphabet
57
-
58
- stack = [eclosure(0)]
59
-
60
- until stack.empty?
61
- state = stack.pop
62
- next if marked[state] || state.empty?
63
-
64
- marked[state] = true
65
-
66
- alphabet.each do |alpha|
67
- next_state = eclosure(following_states(state, alpha))
68
- next if next_state.empty?
69
-
70
- gt[state_id[state], state_id[next_state]] = alpha
71
- stack << next_state
72
- end
73
- end
74
-
75
- final_groups = state_id.keys.find_all { |s|
76
- s.sort.last == accepting
77
- }
78
-
79
- final_groups.each do |states|
80
- id = state_id[states]
81
-
82
- gt.add_accepting(id)
83
- save = states.find { |s|
84
- @memos.key?(s) && eclosure(s).sort.last == accepting
85
- }
86
-
87
- gt.add_memo(id, memo(save))
88
- end
89
-
90
- gt
91
- end
92
-
93
48
  # Returns set of NFA states to which there is a transition on ast symbol
94
49
  # +a+ from some state +s+ in +t+.
95
50
  def following_states(t, a)
@@ -107,7 +62,7 @@ module ActionDispatch
107
62
  end
108
63
 
109
64
  def alphabet
110
- inverted.values.flat_map(&:keys).compact.uniq.sort_by { |x| x.to_s }
65
+ inverted.values.flat_map(&:keys).compact.uniq.sort_by(&:to_s)
111
66
  end
112
67
 
113
68
  # Returns a set of NFA states reachable from some NFA state +s+ in set
@@ -14,15 +14,15 @@ module ActionDispatch
14
14
  end
15
15
 
16
16
  def each(&block)
17
- Visitors::Each.new(block).accept(self)
17
+ Visitors::Each::INSTANCE.accept(self, block)
18
18
  end
19
19
 
20
20
  def to_s
21
- Visitors::String.new.accept(self)
21
+ Visitors::String::INSTANCE.accept(self, '')
22
22
  end
23
23
 
24
24
  def to_dot
25
- Visitors::Dot.new.accept(self)
25
+ Visitors::Dot::INSTANCE.accept(self)
26
26
  end
27
27
 
28
28
  def to_sym
@@ -30,7 +30,7 @@ module ActionDispatch
30
30
  end
31
31
 
32
32
  def name
33
- left.tr '*:', ''
33
+ left.tr '*:'.freeze, ''.freeze
34
34
  end
35
35
 
36
36
  def type
@@ -39,10 +39,15 @@ module ActionDispatch
39
39
 
40
40
  def symbol?; false; end
41
41
  def literal?; false; end
42
+ def terminal?; false; end
43
+ def star?; false; end
44
+ def cat?; false; end
45
+ def group?; false; end
42
46
  end
43
47
 
44
48
  class Terminal < Node # :nodoc:
45
49
  alias :symbol :left
50
+ def terminal?; true; end
46
51
  end
47
52
 
48
53
  class Literal < Terminal # :nodoc:
@@ -69,11 +74,13 @@ module ActionDispatch
69
74
  class Symbol < Terminal # :nodoc:
70
75
  attr_accessor :regexp
71
76
  alias :symbol :regexp
77
+ attr_reader :name
72
78
 
73
79
  DEFAULT_EXP = /[^\.\/\?]+/
74
80
  def initialize(left)
75
81
  super
76
82
  @regexp = DEFAULT_EXP
83
+ @name = left.tr '*:'.freeze, ''.freeze
77
84
  end
78
85
 
79
86
  def default_regexp?
@@ -89,9 +96,11 @@ module ActionDispatch
89
96
 
90
97
  class Group < Unary # :nodoc:
91
98
  def type; :GROUP; end
99
+ def group?; true; end
92
100
  end
93
101
 
94
102
  class Star < Unary # :nodoc:
103
+ def star?; true; end
95
104
  def type; :STAR; end
96
105
 
97
106
  def name
@@ -111,6 +120,7 @@ module ActionDispatch
111
120
  end
112
121
 
113
122
  class Cat < Binary # :nodoc:
123
+ def cat?; true; end
114
124
  def type; :CAT; end
115
125
  end
116
126
 
@@ -6,6 +6,10 @@ module ActionDispatch
6
6
  class Parser < Racc::Parser # :nodoc:
7
7
  include Journey::Nodes
8
8
 
9
+ def self.parse(string)
10
+ new.parse string
11
+ end
12
+
9
13
  def initialize
10
14
  @scanner = Scanner.new
11
15
  end
@@ -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,88 @@
1
1
  module ActionDispatch
2
2
  module Journey # :nodoc:
3
3
  class Route # :nodoc:
4
- attr_reader :app, :path, :defaults, :name
4
+ attr_reader :app, :path, :defaults, :name, :precedence
5
5
 
6
- attr_reader :constraints
6
+ attr_reader :constraints, :internal
7
7
  alias :conditions :constraints
8
8
 
9
- attr_accessor :precedence
9
+ module VerbMatchers
10
+ VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
11
+ VERBS.each do |v|
12
+ class_eval <<-eoc
13
+ class #{v}
14
+ def self.verb; name.split("::").last; end
15
+ def self.call(req); req.#{v.downcase}?; end
16
+ end
17
+ eoc
18
+ end
19
+
20
+ class Unknown
21
+ attr_reader :verb
22
+
23
+ def initialize(verb)
24
+ @verb = verb
25
+ end
26
+
27
+ def call(request); @verb === request.request_method; end
28
+ end
29
+
30
+ class All
31
+ def self.call(_); true; end
32
+ def self.verb; ''; end
33
+ end
34
+
35
+ VERB_TO_CLASS = VERBS.each_with_object({ :all => All }) do |verb, hash|
36
+ klass = const_get verb
37
+ hash[verb] = klass
38
+ hash[verb.downcase] = klass
39
+ hash[verb.downcase.to_sym] = klass
40
+ end
41
+
42
+ end
43
+
44
+ def self.verb_matcher(verb)
45
+ VerbMatchers::VERB_TO_CLASS.fetch(verb) do
46
+ VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
47
+ end
48
+ end
49
+
50
+ def self.build(name, app, path, constraints, required_defaults, defaults)
51
+ request_method_match = verb_matcher(constraints.delete(:request_method))
52
+ new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
53
+ end
10
54
 
11
55
  ##
12
56
  # +path+ is a path constraint.
13
57
  # +constraints+ is a hash of constraints to be applied to this route.
14
- def initialize(name, app, path, constraints, defaults = {})
58
+ def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false)
15
59
  @name = name
16
60
  @app = app
17
61
  @path = path
18
62
 
63
+ @request_method_match = request_method_match
19
64
  @constraints = constraints
20
65
  @defaults = defaults
21
66
  @required_defaults = nil
67
+ @_required_defaults = required_defaults
22
68
  @required_parts = nil
23
69
  @parts = nil
24
70
  @decorated_ast = nil
25
- @precedence = 0
71
+ @precedence = precedence
26
72
  @path_formatter = @path.build_formatter
73
+ @internal = internal
27
74
  end
28
75
 
29
76
  def ast
30
77
  @decorated_ast ||= begin
31
78
  decorated_ast = path.ast
32
- decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
79
+ decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
33
80
  decorated_ast
34
81
  end
35
82
  end
36
83
 
37
84
  def requirements # :nodoc:
38
- # needed for rails `rake routes`
85
+ # needed for rails `rails routes`
39
86
  @defaults.merge(path.requirements).delete_if { |_,v|
40
87
  /.+?/ == v
41
88
  }
@@ -60,7 +107,7 @@ module ActionDispatch
60
107
  end
61
108
 
62
109
  def parts
63
- @parts ||= segments.map { |n| n.to_sym }
110
+ @parts ||= segments.map(&:to_sym)
64
111
  end
65
112
  alias :segment_keys :parts
66
113
 
@@ -68,16 +115,12 @@ module ActionDispatch
68
115
  @path_formatter.evaluate path_options
69
116
  end
70
117
 
71
- def optional_parts
72
- path.optional_names.map { |n| n.to_sym }
73
- end
74
-
75
118
  def required_parts
76
- @required_parts ||= path.required_names.map { |n| n.to_sym }
119
+ @required_parts ||= path.required_names.map(&:to_sym)
77
120
  end
78
121
 
79
122
  def required_default?(key)
80
- (constraints[:required_defaults] || []).include?(key)
123
+ @_required_defaults.include?(key)
81
124
  end
82
125
 
83
126
  def required_defaults
@@ -95,9 +138,8 @@ module ActionDispatch
95
138
  end
96
139
 
97
140
  def matches?(request)
98
- constraints.all? do |method, value|
99
- next true unless request.respond_to?(method)
100
-
141
+ match_verb(request) &&
142
+ constraints.all? { |method, value|
101
143
  case value
102
144
  when Regexp, String
103
145
  value === request.send(method).to_s
@@ -110,15 +152,28 @@ module ActionDispatch
110
152
  else
111
153
  value === request.send(method)
112
154
  end
113
- end
155
+ }
114
156
  end
115
157
 
116
158
  def ip
117
159
  constraints[:ip] || //
118
160
  end
119
161
 
162
+ def requires_matching_verb?
163
+ !@request_method_match.all? { |x| x == VerbMatchers::All }
164
+ end
165
+
120
166
  def verb
121
- constraints[:request_method] || //
167
+ verbs.join('|')
168
+ end
169
+
170
+ private
171
+ def verbs
172
+ @request_method_match.map(&:verb)
173
+ end
174
+
175
+ def match_verb(request)
176
+ @request_method_match.any? { |m| m.call request }
122
177
  end
123
178
  end
124
179
  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,8 +64,8 @@ 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
 
@@ -88,7 +84,7 @@ module ActionDispatch
88
84
  end
89
85
 
90
86
  def custom_routes
91
- partitioned_routes.last
87
+ routes.custom_routes
92
88
  end
93
89
 
94
90
  def filter_routes(path)
@@ -102,7 +98,7 @@ module ActionDispatch
102
98
  }
103
99
 
104
100
  routes =
105
- if req.request_method == "HEAD"
101
+ if req.head?
106
102
  match_head_routes(routes, req)
107
103
  else
108
104
  match_routes(routes, req)
@@ -121,7 +117,7 @@ module ActionDispatch
121
117
  end
122
118
 
123
119
  def match_head_routes(routes, req)
124
- verb_specific_routes = routes.reject { |route| route.verb == // }
120
+ verb_specific_routes = routes.select(&:requires_matching_verb?)
125
121
  head_routes = match_routes(verb_specific_routes, req)
126
122
 
127
123
  if head_routes.empty?