ruby_routes 2.5.0 → 2.7.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/ruby_routes/cache_setup.rb +32 -0
  4. data/lib/ruby_routes/constant.rb +18 -3
  5. data/lib/ruby_routes/lru_strategies/hit_strategy.rb +1 -6
  6. data/lib/ruby_routes/node.rb +14 -87
  7. data/lib/ruby_routes/radix_tree/finder.rb +88 -46
  8. data/lib/ruby_routes/radix_tree/inserter.rb +2 -55
  9. data/lib/ruby_routes/radix_tree/traversal_strategy/base.rb +18 -0
  10. data/lib/ruby_routes/radix_tree/traversal_strategy/generic_loop.rb +25 -0
  11. data/lib/ruby_routes/radix_tree/traversal_strategy/unrolled.rb +54 -0
  12. data/lib/ruby_routes/radix_tree/traversal_strategy.rb +26 -0
  13. data/lib/ruby_routes/radix_tree.rb +12 -62
  14. data/lib/ruby_routes/route/check_helpers.rb +2 -2
  15. data/lib/ruby_routes/route/constraint_validator.rb +24 -1
  16. data/lib/ruby_routes/route/matcher.rb +11 -0
  17. data/lib/ruby_routes/route/param_support.rb +9 -8
  18. data/lib/ruby_routes/route/path_builder.rb +11 -6
  19. data/lib/ruby_routes/route/path_generation.rb +5 -1
  20. data/lib/ruby_routes/route/small_lru.rb +51 -5
  21. data/lib/ruby_routes/route/validation_helpers.rb +6 -46
  22. data/lib/ruby_routes/route.rb +32 -59
  23. data/lib/ruby_routes/route_set/cache_helpers.rb +24 -25
  24. data/lib/ruby_routes/route_set/collection_helpers.rb +7 -16
  25. data/lib/ruby_routes/route_set.rb +36 -59
  26. data/lib/ruby_routes/router/build_helpers.rb +1 -7
  27. data/lib/ruby_routes/router/builder.rb +12 -12
  28. data/lib/ruby_routes/router/http_helpers.rb +7 -48
  29. data/lib/ruby_routes/router/resource_helpers.rb +23 -37
  30. data/lib/ruby_routes/router/scope_helpers.rb +26 -14
  31. data/lib/ruby_routes/router.rb +28 -29
  32. data/lib/ruby_routes/segment.rb +3 -3
  33. data/lib/ruby_routes/segments/base_segment.rb +8 -0
  34. data/lib/ruby_routes/segments/dynamic_segment.rb +1 -1
  35. data/lib/ruby_routes/segments/static_segment.rb +3 -1
  36. data/lib/ruby_routes/strategies/base.rb +18 -0
  37. data/lib/ruby_routes/strategies/hash_based_strategy.rb +33 -0
  38. data/lib/ruby_routes/strategies/hybrid_strategy.rb +70 -0
  39. data/lib/ruby_routes/strategies/radix_tree_strategy.rb +24 -0
  40. data/lib/ruby_routes/strategies.rb +5 -0
  41. data/lib/ruby_routes/utility/key_builder_utility.rb +4 -26
  42. data/lib/ruby_routes/utility/method_utility.rb +11 -11
  43. data/lib/ruby_routes/utility/path_utility.rb +19 -7
  44. data/lib/ruby_routes/version.rb +1 -1
  45. data/lib/ruby_routes.rb +3 -1
  46. metadata +12 -2
  47. data/lib/ruby_routes/string_extensions.rb +0 -65
@@ -9,38 +9,14 @@ require_relative 'radix_tree/inserter'
9
9
  require_relative 'radix_tree/finder'
10
10
 
11
11
  module RubyRoutes
12
- # RadixTree
13
- #
14
- # Compact routing trie supporting:
15
- # - Static segments (/users)
16
- # - Dynamic segments (/users/:id)
17
- # - Wildcard splat (/assets/*path)
18
- #
19
- # Design / Behavior:
20
- # - Traversal keeps track of the deepest valid endpoint encountered
21
- # (best_match_node) so that if a later branch fails, we can still
22
- # return a shorter matching route.
23
- # - Dynamic and wildcard captures are written directly into the caller
24
- # supplied params Hash (or a fresh one) to avoid intermediate objects.
25
- # - A very small manual LRU (Hash + order Array) caches the result of
26
- # splitting raw paths into their segment arrays.
27
- #
28
- # Matching Precedence:
29
- # static > dynamic > wildcard
30
- #
31
- # Thread Safety:
32
- # - Not thread‑safe for mutation (intended for boot‑time construction).
33
- # Safe for concurrent reads after routes are added.
34
- #
35
- # @api internal
36
12
  class RadixTree
37
13
  include RubyRoutes::Utility::PathUtility
38
14
  include RubyRoutes::Utility::MethodUtility
39
- include Inserter
40
- include Finder
15
+ include RubyRoutes::RadixTree::Inserter
16
+ include RubyRoutes::RadixTree::Finder
41
17
 
42
18
  class << self
43
- # Backwards DSL convenience: RadixTree.new(args) Route
19
+ # Allow RadixTree.new(path, options...) to act as a convenience factory
44
20
  def new(*args, &block)
45
21
  if args.any?
46
22
  RubyRoutes::Route.new(*args, &block)
@@ -50,48 +26,22 @@ module RubyRoutes
50
26
  end
51
27
  end
52
28
 
53
- # Initialize empty tree and split cache.
54
29
  def initialize
55
- @root = Node.new
56
- @split_cache = RubyRoutes::Route::SmallLru.new(2048)
57
- @split_cache_max = 2048
58
- @split_cache_order = []
59
- @empty_segment_list = [].freeze
30
+ @root = Node.new
31
+ @split_cache = RubyRoutes::Route::SmallLru.new(RubyRoutes::Constant::CACHE_SIZE)
32
+ @split_cache_max = RubyRoutes::Constant::CACHE_SIZE # larger cache for better hit rates
33
+ @empty_segments = [].freeze # reuse for root path
60
34
  end
61
35
 
62
- # Add a route to the tree (delegates insertion logic).
63
- #
64
- # @param raw_path [String]
65
- # @param http_methods [Array<String,Symbol>]
66
- # @param route_handler [Object]
67
- # @return [Object] route_handler
68
- def add(raw_path, http_methods, route_handler)
69
- normalized_path = normalize_path(raw_path)
70
- normalized_methods = http_methods.map { |m| normalize_http_method(m) }
71
- insert_route(normalized_path, normalized_methods, route_handler)
72
- route_handler
73
- end
74
-
75
- # Public find delegates to Finder#find (now simplified on this class).
76
- def find(request_path_input, request_method_input, params_out = {})
77
- super
36
+ def add(path, methods, handler)
37
+ insert_route(path, methods, handler)
78
38
  end
79
39
 
80
40
  private
81
41
 
82
- # Split path with small manual LRU cache.
83
- #
84
- # @param raw_path [String]
85
- # @return [Array<String>]
86
- def split_path_cached(raw_path)
87
- return @empty_segment_list if raw_path == '/'
88
-
89
- cached = @split_cache.get(raw_path)
90
- return cached if cached
91
-
92
- segments = split_path(raw_path)
93
- @split_cache.set(raw_path, segments)
94
- segments
42
+ # Cached path splitting with optimized common cases
43
+ def split_path_cached(path)
44
+ @split_cache.get(path) || @split_cache.set(path, split_path(path))
95
45
  end
96
46
  end
97
47
  end
@@ -20,7 +20,7 @@ module RubyRoutes
20
20
  # @raise [RubyRoutes::ConstraintViolation] If the value is shorter than the minimum length.
21
21
  # @return [void]
22
22
  def check_min_length(constraint, value)
23
- return unless (min = constraint[:min_length]) && value && value.length < min
23
+ return unless (min = constraint[:min_length]) && value&.length < min
24
24
 
25
25
  raise RubyRoutes::ConstraintViolation, "Value too short (minimum #{constraint[:min_length]} characters)"
26
26
  end
@@ -36,7 +36,7 @@ module RubyRoutes
36
36
  # @raise [RubyRoutes::ConstraintViolation] If the value exceeds the maximum length.
37
37
  # @return [void]
38
38
  def check_max_length(constraint, value)
39
- return unless (max = constraint[:max_length]) && value && value.length > max
39
+ return unless (max = constraint[:max_length]) && value&.length > max
40
40
 
41
41
  raise RubyRoutes::ConstraintViolation, "Value too long (maximum #{constraint[:max_length]} characters)"
42
42
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'timeout'
4
4
  require_relative '../constant'
5
+ require_relative 'warning_helpers'
6
+ require_relative 'check_helpers'
5
7
 
6
8
  module RubyRoutes
7
9
  class Route
@@ -12,6 +14,8 @@ module RubyRoutes
12
14
  # validation rules. It also handles timeouts and raises appropriate exceptions
13
15
  # for constraint violations.
14
16
  module ConstraintValidator
17
+ include RubyRoutes::Route::WarningHelpers
18
+ include RubyRoutes::Route::CheckHelpers
15
19
  # Validate all constraints for the given parameters.
16
20
  #
17
21
  # This method iterates through all constraints and validates each parameter
@@ -47,6 +51,24 @@ module RubyRoutes
47
51
  end
48
52
  end
49
53
 
54
+ # Validate hash-form constraint rules.
55
+ #
56
+ # This method validates a value against a set of hash-form constraints,
57
+ # such as minimum length, maximum length, format, inclusion, exclusion,
58
+ # and range.
59
+ #
60
+ # @param constraint [Hash] The constraint rules.
61
+ # @param value [String] The value to validate.
62
+ # @raise [RubyRoutes::ConstraintViolation] If the value violates any constraint.
63
+ # @return [void]
64
+ def validate_hash_constraint!(constraint, value)
65
+ check_min_length(constraint, value)
66
+ check_max_length(constraint, value)
67
+ check_format(constraint, value)
68
+ check_in_list(constraint, value)
69
+ check_not_in_list(constraint, value)
70
+ check_range(constraint, value)
71
+ end
50
72
  # Handle built-in symbol/string rules via a simple lookup.
51
73
  #
52
74
  # @param rule [Symbol, String] The built-in constraint rule.
@@ -124,7 +146,8 @@ module RubyRoutes
124
146
  # @param value [Object] The value to validate.
125
147
  # @raise [RubyRoutes::ConstraintViolation] If the value is not a valid email.
126
148
  def validate_email_constraint(value)
127
- invalid! unless value.to_s.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
149
+ email_regex = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+\z/
150
+ invalid! unless value.to_s.match?(email_regex)
128
151
  end
129
152
 
130
153
  # Validate a slug constraint.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyRoutes
4
+ class Route
5
+ module Matcher
6
+ def match(path, method)
7
+ raise NotImplementedError
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'path_generation'
4
3
  require_relative 'warning_helpers'
5
- require_relative 'constraint_validator'
4
+ require_relative 'query_helpers'
6
5
 
7
6
  module RubyRoutes
8
7
  class Route
@@ -15,6 +14,7 @@ module RubyRoutes
15
14
  # Thread-safety: Thread-local storage is used to avoid allocation and cross-thread mutation.
16
15
  module ParamSupport
17
16
  include RubyRoutes::Route::WarningHelpers
17
+ include RubyRoutes::Route::QueryHelpers
18
18
 
19
19
  private
20
20
 
@@ -26,11 +26,9 @@ module RubyRoutes
26
26
  # @param params [Hash] The user-provided parameters.
27
27
  # @return [Hash] The merged parameters.
28
28
  def build_merged_params(params)
29
- return @defaults if params.nil? || params.empty?
30
-
31
29
  merged_hash = acquire_merge_hash
32
30
  merge_defaults_into(merged_hash)
33
- merge_user_params_into(merged_hash, params)
31
+ merge_user_params_into(merged_hash, params) unless params.nil? || params.empty?
34
32
  merged_hash
35
33
  end
36
34
 
@@ -60,9 +58,12 @@ module RubyRoutes
60
58
  # @return [void]
61
59
  def merge_user_params_into(merged_hash, params)
62
60
  params.each do |key, value|
63
- next if value.nil?
64
-
65
- merged_hash[key.is_a?(String) ? key : key.to_s] = value
61
+ string_key = if key.is_a?(String)
62
+ key
63
+ else
64
+ (Thread.current[:ruby_routes_symbol_keys] ||= {})[key] ||= key.to_s.freeze
65
+ end
66
+ merged_hash[string_key] = value
66
67
  end
67
68
  end
68
69
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'small_lru'
4
+
3
5
  module RubyRoutes
4
6
  class Route
5
7
  # PathBuilder: generation + segment encoding
@@ -25,7 +27,7 @@ module RubyRoutes
25
27
  # Append a segment to the buffer.
26
28
  #
27
29
  # @param buffer [String] the buffer to append to
28
- # @param segment [Hash] the segment to append
30
+ # @param segment [RubyRoutes::Segments::BaseSegment] the segment to append
29
31
  # @param merged [Hash] the merged parameters
30
32
  # @param index [Integer] the current index
31
33
  # @param last_index [Integer] the last index
@@ -48,8 +50,8 @@ module RubyRoutes
48
50
  # Rough heuristic (static sizes + average dynamic)
49
51
  base = 1
50
52
  @compiled_segments.each do |segment|
51
- base += case segment[:type]
52
- when :static then segment[:value].length + 1
53
+ base += case segment
54
+ when RubyRoutes::Segments::StaticSegment then segment.literal_text.length + 1
53
55
  else 20
54
56
  end
55
57
  end
@@ -75,9 +77,12 @@ module RubyRoutes
75
77
  def encode_segment_fast(string)
76
78
  return string if RubyRoutes::Constant::UNRESERVED_RE.match?(string)
77
79
 
78
- @encoding_cache ||= {}
79
- # Use gsub instead of tr for proper replacement of + with %20
80
- @encoding_cache[string] ||= URI.encode_www_form_component(string).gsub('+', '%20')
80
+ @encoding_cache ||= RubyRoutes::Route::SmallLru.new(256)
81
+ cached = @encoding_cache.get(string)
82
+ return cached if cached
83
+
84
+ encoded = URI.encode_www_form_component(string).gsub('+', '%20')
85
+ @encoding_cache.set(string, encoded)
81
86
  end
82
87
  end
83
88
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'warning_helpers'
4
+ require_relative 'validation_helpers'
5
+ require_relative 'param_support'
4
6
 
5
7
  module RubyRoutes
6
8
  class Route
@@ -9,6 +11,8 @@ module RubyRoutes
9
11
  # route-related warnings (kept separate to reduce parent module size).
10
12
  module PathGeneration
11
13
  include RubyRoutes::Route::WarningHelpers
14
+ include RubyRoutes::Route::ValidationHelpers
15
+ include RubyRoutes::Route::ParamSupport
12
16
 
13
17
  private
14
18
 
@@ -25,8 +29,8 @@ module RubyRoutes
25
29
  return @static_path if static_short_circuit?(params)
26
30
  return @static_path || RubyRoutes::Constant::ROOT_PATH if trivial_route?
27
31
 
28
- validate_required_once(params)
29
32
  merged_params = build_merged_params(params)
33
+ validate_required_once(merged_params.dup)
30
34
 
31
35
  build_or_fetch_generated_path(merged_params)
32
36
  end
@@ -27,7 +27,7 @@ module RubyRoutes
27
27
  # @return [Integer] number of cache hits
28
28
  # @return [Integer] number of cache misses
29
29
  # @return [Integer] number of evictions (when capacity exceeded)
30
- attr_reader :max_size, :hits, :misses, :evictions
30
+ attr_reader :max_size, :hits, :misses, :evictions, :hash
31
31
 
32
32
  # @param max_size [Integer] positive maximum size
33
33
  # @raise [ArgumentError] if max_size < 1
@@ -91,6 +91,42 @@ module RubyRoutes
91
91
  @hash.keys
92
92
  end
93
93
 
94
+ # Check if a key exists in the cache.
95
+ #
96
+ # @param key [Object] The key to check.
97
+ # @return [Boolean] True if the key exists, false otherwise.
98
+ def has_key?(key)
99
+ @hash.key?(key)
100
+ end
101
+
102
+ # Hash-like access for reading (delegates to get).
103
+ #
104
+ # @param key [Object] The key to retrieve.
105
+ # @return [Object, nil] The cached value or nil.
106
+ def [](key)
107
+ get(key)
108
+ end
109
+
110
+ # Include matcher (checks for key-value pair).
111
+ #
112
+ # @param pair [Hash] A hash with a single key-value pair (e.g., { 'key' => 'value' }).
113
+ # @return [Boolean] True if the pair exists in the cache, false otherwise.
114
+ def include?(pair)
115
+ return false unless pair.is_a?(Hash) && pair.size == 1
116
+
117
+ key, value = pair.first
118
+ @hash[key] == value
119
+ end
120
+
121
+ # Hash-like access for writing (delegates to set).
122
+ #
123
+ # @param key [Object] The key to set.
124
+ # @param value [Object] The value to cache.
125
+ # @return [Object] The value.
126
+ def []=(key, value)
127
+ set(key, value)
128
+ end
129
+
94
130
  # Debug / spec helper (avoid exposing internal Hash directly).
95
131
  # @return [Hash] shallow copy of internal store
96
132
  def inspect_hash
@@ -109,14 +145,24 @@ module RubyRoutes
109
145
  @misses += 1
110
146
  end
111
147
 
148
+ def clear_counters!
149
+ @hits = 0
150
+ @misses = 0
151
+ @evictions = 0
152
+ end
153
+
112
154
  # Internal helper used by hit strategy to promote key.
113
- # @param key [Object]
114
- # @return [void]
155
+ # Moves the key-value pair to the end of the hash (most recently used position).
156
+ #
157
+ # @param key [Object] The key to promote
158
+ # @return [Object, nil] The value associated with the key, or nil if not found
115
159
  def promote(key)
160
+ return nil unless @hash.key?(key)
161
+
116
162
  val = @hash.delete(key)
117
- @hash[key] = val if val
163
+ @hash[key] = val
164
+ val
118
165
  end
119
- private :promote
120
166
  end
121
167
  end
122
168
  end
@@ -12,16 +12,6 @@ module RubyRoutes
12
12
  module ValidationHelpers
13
13
  include RubyRoutes::Route::CheckHelpers
14
14
 
15
- # Initialize validation result cache.
16
- #
17
- # This method initializes an LRU (Least Recently Used) cache for storing
18
- # validation results, with a maximum size of 64 entries.
19
- #
20
- # @return [void]
21
- def initialize_validation_cache
22
- @validation_cache = SmallLru.new(64)
23
- end
24
-
25
15
  # Validate fundamental route shape.
26
16
  #
27
17
  # This method ensures that the route has a valid controller, action, and
@@ -48,20 +38,14 @@ module RubyRoutes
48
38
  def validate_required_once(params)
49
39
  return if @required_params.empty?
50
40
 
51
- # Check cache for existing validation result
52
41
  cached_result = get_cached_validation(params)
53
42
  if cached_result
54
43
  missing, nils = cached_result
55
44
  else
56
- # Perform validation
57
45
  missing, nils = validate_required_params(params)
58
- # Cache the result only if params are frozen
59
- if params.frozen?
60
- cache_validation_result(params, [missing, nils])
61
- end
46
+ cache_validation_result(params.freeze, [missing, nils])
62
47
  end
63
48
 
64
- # Raise if invalid
65
49
  raise RouteNotFound, "Missing params: #{missing.join(', ')}" unless missing.empty?
66
50
  raise RouteNotFound, "Missing or nil params: #{nils.join(', ')}" unless nils.empty?
67
51
  end
@@ -78,10 +62,6 @@ module RubyRoutes
78
62
  return RubyRoutes::Constant::EMPTY_PAIR if @required_params.empty?
79
63
  params ||= {}
80
64
 
81
- if (cached = get_cached_validation(params))
82
- return cached
83
- end
84
-
85
65
  missing = []
86
66
  nils = []
87
67
  @required_params.each do |required_key|
@@ -124,14 +104,13 @@ module RubyRoutes
124
104
  # @return [void]
125
105
  def cache_validation_result(params, result)
126
106
  return unless params.frozen?
127
- return unless @validation_cache && @validation_cache.size < 64
107
+ return unless @validation_cache
128
108
 
129
- @cache_mutex.synchronize { @validation_cache.set(params.hash, result) }
109
+ @cache_mutex.synchronize do
110
+ return if @validation_cache.size >= 64
111
+ @validation_cache.set(params.hash, result)
112
+ end
130
113
  end
131
-
132
- # Fetch cached validation result.
133
- #
134
- # This method retrieves a cached validation result for the given parameters.
135
114
  #
136
115
  # @param params [Hash] The parameters used for validation.
137
116
  # @return [Object, nil] The cached validation result, or `nil` if not found.
@@ -150,25 +129,6 @@ module RubyRoutes
150
129
  pool = Thread.current[:ruby_routes_hash_pool] ||= []
151
130
  pool.push(hash) if pool.size < 5
152
131
  end
153
-
154
- # Validate hash-form constraint rules.
155
- #
156
- # This method validates a value against a set of hash-form constraints,
157
- # such as minimum length, maximum length, format, inclusion, exclusion,
158
- # and range.
159
- #
160
- # @param constraint [Hash] The constraint rules.
161
- # @param value [String] The value to validate.
162
- # @raise [RubyRoutes::ConstraintViolation] If the value violates any constraint.
163
- # @return [void]
164
- def validate_hash_constraint!(constraint, value)
165
- check_min_length(constraint, value)
166
- check_max_length(constraint, value)
167
- check_format(constraint, value)
168
- check_in_list(constraint, value)
169
- check_not_in_list(constraint, value)
170
- check_range(constraint, value)
171
- end
172
132
  end
173
133
  end
174
134
  end
@@ -6,18 +6,21 @@ require 'rack'
6
6
  require 'set'
7
7
  require_relative 'constant'
8
8
  require_relative 'node'
9
+ require_relative 'cache_setup'
10
+ require_relative 'route_set/cache_helpers'
9
11
  require_relative 'route/small_lru'
10
12
  require_relative 'utility/key_builder_utility'
11
13
  require_relative 'utility/method_utility'
12
14
  require_relative 'utility/path_utility'
13
15
  require_relative 'utility/route_utility'
14
16
  require_relative 'route/param_support'
15
- require_relative 'route/segment_compiler'
17
+ require_relative 'segment'
16
18
  require_relative 'route/path_builder'
17
19
  require_relative 'route/constraint_validator'
18
20
  require_relative 'route/check_helpers'
19
21
  require_relative 'route/query_helpers'
20
22
  require_relative 'route/validation_helpers'
23
+ require_relative 'route/segment_compiler'
21
24
  require_relative 'route/path_generation'
22
25
 
23
26
  module RubyRoutes
@@ -51,43 +54,44 @@ module RubyRoutes
51
54
  # @api public
52
55
  class Route
53
56
  include ParamSupport
54
- include SegmentCompiler
55
57
  include PathBuilder
56
58
  include RubyRoutes::Route::ConstraintValidator
57
59
  include RubyRoutes::Route::ValidationHelpers
58
60
  include RubyRoutes::Route::QueryHelpers
59
61
  include RubyRoutes::Route::PathGeneration
62
+ include RubyRoutes::Route::SegmentCompiler
60
63
  include RubyRoutes::Utility::MethodUtility
61
64
  include RubyRoutes::Utility::PathUtility
62
65
  include RubyRoutes::Utility::KeyBuilderUtility
66
+ include RubyRoutes::RouteSet::CacheHelpers
67
+ include RubyRoutes::CacheSetup
63
68
 
64
69
  attr_reader :path, :methods, :controller, :action, :name, :constraints, :defaults
65
70
 
66
- public :extract_params, :parse_query_params, :query_params, :generate_path
71
+ public :extract_params, :parse_query_params, :query_params, :generate_path, :merge_query_params_into_hash
67
72
 
68
73
  # Create a new Route.
69
74
  #
70
75
  # @param path [String] The raw route path (may include `:params` or `*splat`).
71
76
  # @param options [Hash] The options for the route.
72
77
  # @option options [Symbol, String, Array<Symbol, String>] :via (:get) HTTP method(s).
73
- # @option options [String] :to ("controller#action") The controller and action.
74
- # @option options [String] :controller Explicit controller (overrides `:to`).
75
- # @option options [String, Symbol] :action Explicit action (overrides part after `#`).
76
- # @option options [Hash] :constraints Parameter constraints (Regexp / Symbol / Hash).
77
- # @option options [Hash] :defaults Default parameter values.
78
- # @option options [Symbol, String] :as The route name.
78
+ # @option options [String] :to The controller#action string.
79
+ # @option options [String] :controller The controller name.
80
+ # @option options [String] :action The action name.
81
+ # @option options [String] :as The route name.
82
+ # @option options [Hash] :constraints The route constraints.
83
+ # @option options [Hash] :defaults The route defaults.
79
84
  def initialize(path, options = {})
85
+ @options = options
80
86
  @path = normalize_path(path)
81
-
82
- setup_methods(options)
83
- setup_controller_and_action(options)
84
-
85
- @name = options[:as]
86
- @constraints = (options[:constraints] || {}).freeze
87
- @defaults = (options[:defaults] || {}).transform_keys(&:to_s).freeze
87
+ @name = @options[:as]
88
+ @constraints = (@options[:constraints] || {}).freeze
89
+ @defaults = (@options[:defaults] || {}).transform_keys(&:to_s).freeze
88
90
  @param_key_slots = [[nil, nil], [nil, nil]]
89
91
  @required_validated_once = false
90
92
 
93
+ setup_caches
94
+
91
95
  precompile_route_data
92
96
  validate_route!
93
97
  end
@@ -121,56 +125,25 @@ module RubyRoutes
121
125
 
122
126
  private
123
127
 
124
- # Set up HTTP methods from options.
128
+ # Expose for testing / external callers.
129
+ public :extract_path_params_fast
130
+
131
+ # Precompile route data for performance.
125
132
  #
126
- # @param options [Hash] The options for the route.
127
133
  # @return [void]
128
- def setup_methods(options)
129
- raw_http_methods = Array(options[:via] || :get)
134
+ def precompile_route_data
135
+ raw_http_methods = Array(@options[:via] || :get)
130
136
  @methods = raw_http_methods.map { |method| normalize_http_method(method) }.freeze
131
137
  @methods_set = @methods.to_set.freeze
132
- end
133
-
134
- # Set up controller and action from options.
135
- #
136
- # @param options [Hash] The options for the route.
137
- # @return [void]
138
- def setup_controller_and_action(options)
139
- @controller = extract_controller(options)
140
- @action = options[:action] || extract_action(options[:to])
141
- end
142
-
143
- # Infer controller name from options or `:to`.
144
- #
145
- # @param options [Hash] The options for the route.
146
- # @return [String, nil] The inferred controller name.
147
- def extract_controller(options)
148
- return options[:controller] if options[:controller]
149
- to = options[:to]
150
- return nil unless to
151
-
152
- to.to_s.split('#', 2).first
153
- end
154
-
155
- # Infer action from `:to` string.
156
- #
157
- # @param to [String, nil] The `:to` string.
158
- # @return [String, nil] The inferred action name.
159
- def extract_action(to)
160
- return nil unless to
161
138
 
162
- to.to_s.split('#', 2).last
163
- end
139
+ to_str = @options[:to].to_s
140
+ to_controller, to_action = to_str.split('#', 2)
141
+ @controller = @options[:controller] || to_controller
142
+ @action = @options[:action] || to_action
164
143
 
165
- # Precompile route data for performance.
166
- #
167
- # @return [void]
168
- def precompile_route_data
169
144
  @is_resource = @path.match?(%r{/:id(?:$|\.)})
170
- @gen_cache = SmallLru.new(512)
171
- @query_cache = SmallLru.new(RubyRoutes::Constant::QUERY_CACHE_SIZE)
172
- @cache_mutex = Mutex.new # Thread-safe access to caches
173
- initialize_validation_cache
145
+
146
+ setup_caches
174
147
  compile_segments
175
148
  compile_required_params
176
149
  check_static_path