ruby_routes 2.5.0 → 2.6.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -23
  3. data/lib/ruby_routes/constant.rb +18 -3
  4. data/lib/ruby_routes/lru_strategies/hit_strategy.rb +1 -1
  5. data/lib/ruby_routes/node.rb +14 -87
  6. data/lib/ruby_routes/radix_tree/finder.rb +75 -47
  7. data/lib/ruby_routes/radix_tree/inserter.rb +2 -55
  8. data/lib/ruby_routes/radix_tree/traversal_strategy/base.rb +18 -0
  9. data/lib/ruby_routes/radix_tree/traversal_strategy/generic_loop.rb +25 -0
  10. data/lib/ruby_routes/radix_tree/traversal_strategy/unrolled.rb +45 -0
  11. data/lib/ruby_routes/radix_tree/traversal_strategy.rb +26 -0
  12. data/lib/ruby_routes/radix_tree.rb +12 -62
  13. data/lib/ruby_routes/route/check_helpers.rb +3 -3
  14. data/lib/ruby_routes/route/constraint_validator.rb +22 -1
  15. data/lib/ruby_routes/route/matcher.rb +11 -0
  16. data/lib/ruby_routes/route/param_support.rb +9 -8
  17. data/lib/ruby_routes/route/path_builder.rb +11 -6
  18. data/lib/ruby_routes/route/path_generation.rb +5 -1
  19. data/lib/ruby_routes/route/small_lru.rb +43 -2
  20. data/lib/ruby_routes/route/validation_helpers.rb +6 -36
  21. data/lib/ruby_routes/route.rb +35 -56
  22. data/lib/ruby_routes/route_set/cache_helpers.rb +29 -13
  23. data/lib/ruby_routes/route_set/collection_helpers.rb +8 -10
  24. data/lib/ruby_routes/route_set.rb +34 -59
  25. data/lib/ruby_routes/router/build_helpers.rb +1 -7
  26. data/lib/ruby_routes/router/builder.rb +12 -12
  27. data/lib/ruby_routes/router/http_helpers.rb +7 -48
  28. data/lib/ruby_routes/router/resource_helpers.rb +23 -37
  29. data/lib/ruby_routes/router/scope_helpers.rb +26 -14
  30. data/lib/ruby_routes/router.rb +28 -29
  31. data/lib/ruby_routes/segment.rb +3 -3
  32. data/lib/ruby_routes/segments/base_segment.rb +8 -0
  33. data/lib/ruby_routes/segments/static_segment.rb +3 -1
  34. data/lib/ruby_routes/strategies/base.rb +18 -0
  35. data/lib/ruby_routes/strategies/hash_based_strategy.rb +33 -0
  36. data/lib/ruby_routes/strategies/hybrid_strategy.rb +70 -0
  37. data/lib/ruby_routes/strategies/radix_tree_strategy.rb +24 -0
  38. data/lib/ruby_routes/strategies.rb +5 -0
  39. data/lib/ruby_routes/utility/key_builder_utility.rb +4 -26
  40. data/lib/ruby_routes/utility/method_utility.rb +11 -11
  41. data/lib/ruby_routes/utility/path_utility.rb +18 -7
  42. data/lib/ruby_routes/version.rb +1 -1
  43. data/lib/ruby_routes.rb +3 -1
  44. metadata +11 -2
  45. 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
@@ -52,7 +52,7 @@ module RubyRoutes
52
52
  # @raise [RubyRoutes::ConstraintViolation] If the value does not match the required format.
53
53
  # @return [void]
54
54
  def check_format(constraint, value)
55
- return unless (format = constraint[:format]) && value && !value.match?(format)
55
+ return unless (format = constraint[:format]) && !value&.match?(format)
56
56
 
57
57
  raise RubyRoutes::ConstraintViolation, 'Value does not match required format'
58
58
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'timeout'
4
4
  require_relative '../constant'
5
+ require_relative 'warning_helpers'
5
6
 
6
7
  module RubyRoutes
7
8
  class Route
@@ -12,6 +13,7 @@ module RubyRoutes
12
13
  # validation rules. It also handles timeouts and raises appropriate exceptions
13
14
  # for constraint violations.
14
15
  module ConstraintValidator
16
+ include RubyRoutes::Route::WarningHelpers
15
17
  # Validate all constraints for the given parameters.
16
18
  #
17
19
  # This method iterates through all constraints and validates each parameter
@@ -47,6 +49,24 @@ module RubyRoutes
47
49
  end
48
50
  end
49
51
 
52
+ # Validate hash-form constraint rules.
53
+ #
54
+ # This method validates a value against a set of hash-form constraints,
55
+ # such as minimum length, maximum length, format, inclusion, exclusion,
56
+ # and range.
57
+ #
58
+ # @param constraint [Hash] The constraint rules.
59
+ # @param value [String] The value to validate.
60
+ # @raise [RubyRoutes::ConstraintViolation] If the value violates any constraint.
61
+ # @return [void]
62
+ def validate_hash_constraint!(constraint, value)
63
+ check_min_length(constraint, value)
64
+ check_max_length(constraint, value)
65
+ check_format(constraint, value)
66
+ check_in_list(constraint, value)
67
+ check_not_in_list(constraint, value)
68
+ check_range(constraint, value)
69
+ end
50
70
  # Handle built-in symbol/string rules via a simple lookup.
51
71
  #
52
72
  # @param rule [Symbol, String] The built-in constraint rule.
@@ -124,7 +144,8 @@ module RubyRoutes
124
144
  # @param value [Object] The value to validate.
125
145
  # @raise [RubyRoutes::ConstraintViolation] If the value is not a valid email.
126
146
  def validate_email_constraint(value)
127
- invalid! unless value.to_s.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
147
+ 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/
148
+ invalid! unless value.to_s.match?(email_regex)
128
149
  end
129
150
 
130
151
  # 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,6 +145,12 @@ 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
155
  # @param key [Object]
114
156
  # @return [void]
@@ -116,7 +158,6 @@ module RubyRoutes
116
158
  val = @hash.delete(key)
117
159
  @hash[key] = val if val
118
160
  end
119
- private :promote
120
161
  end
121
162
  end
122
163
  end
@@ -48,20 +48,14 @@ module RubyRoutes
48
48
  def validate_required_once(params)
49
49
  return if @required_params.empty?
50
50
 
51
- # Check cache for existing validation result
52
51
  cached_result = get_cached_validation(params)
53
52
  if cached_result
54
53
  missing, nils = cached_result
55
54
  else
56
- # Perform validation
57
55
  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
56
+ cache_validation_result(params.freeze, [missing, nils])
62
57
  end
63
58
 
64
- # Raise if invalid
65
59
  raise RouteNotFound, "Missing params: #{missing.join(', ')}" unless missing.empty?
66
60
  raise RouteNotFound, "Missing or nil params: #{nils.join(', ')}" unless nils.empty?
67
61
  end
@@ -78,10 +72,6 @@ module RubyRoutes
78
72
  return RubyRoutes::Constant::EMPTY_PAIR if @required_params.empty?
79
73
  params ||= {}
80
74
 
81
- if (cached = get_cached_validation(params))
82
- return cached
83
- end
84
-
85
75
  missing = []
86
76
  nils = []
87
77
  @required_params.each do |required_key|
@@ -124,14 +114,13 @@ module RubyRoutes
124
114
  # @return [void]
125
115
  def cache_validation_result(params, result)
126
116
  return unless params.frozen?
127
- return unless @validation_cache && @validation_cache.size < 64
117
+ return unless @validation_cache
128
118
 
129
- @cache_mutex.synchronize { @validation_cache.set(params.hash, result) }
119
+ @cache_mutex.synchronize do
120
+ return if @validation_cache.size >= 64
121
+ @validation_cache.set(params.hash, result)
122
+ end
130
123
  end
131
-
132
- # Fetch cached validation result.
133
- #
134
- # This method retrieves a cached validation result for the given parameters.
135
124
  #
136
125
  # @param params [Hash] The parameters used for validation.
137
126
  # @return [Object, nil] The cached validation result, or `nil` if not found.
@@ -150,25 +139,6 @@ module RubyRoutes
150
139
  pool = Thread.current[:ruby_routes_hash_pool] ||= []
151
140
  pool.push(hash) if pool.size < 5
152
141
  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
142
  end
173
143
  end
174
144
  end
@@ -12,13 +12,15 @@ require_relative 'utility/method_utility'
12
12
  require_relative 'utility/path_utility'
13
13
  require_relative 'utility/route_utility'
14
14
  require_relative 'route/param_support'
15
- require_relative 'route/segment_compiler'
15
+ require_relative 'segment'
16
16
  require_relative 'route/path_builder'
17
17
  require_relative 'route/constraint_validator'
18
18
  require_relative 'route/check_helpers'
19
19
  require_relative 'route/query_helpers'
20
20
  require_relative 'route/validation_helpers'
21
+ require_relative 'route/segment_compiler'
21
22
  require_relative 'route/path_generation'
23
+ require_relative 'route_set/cache_helpers'
22
24
 
23
25
  module RubyRoutes
24
26
  # Route
@@ -51,43 +53,43 @@ module RubyRoutes
51
53
  # @api public
52
54
  class Route
53
55
  include ParamSupport
54
- include SegmentCompiler
55
56
  include PathBuilder
56
57
  include RubyRoutes::Route::ConstraintValidator
57
58
  include RubyRoutes::Route::ValidationHelpers
58
59
  include RubyRoutes::Route::QueryHelpers
59
60
  include RubyRoutes::Route::PathGeneration
61
+ include RubyRoutes::Route::SegmentCompiler
60
62
  include RubyRoutes::Utility::MethodUtility
61
63
  include RubyRoutes::Utility::PathUtility
62
64
  include RubyRoutes::Utility::KeyBuilderUtility
65
+ include RubyRoutes::RouteSet::CacheHelpers
63
66
 
64
67
  attr_reader :path, :methods, :controller, :action, :name, :constraints, :defaults
65
68
 
66
- public :extract_params, :parse_query_params, :query_params, :generate_path
69
+ public :extract_params, :parse_query_params, :query_params, :generate_path, :merge_query_params_into_hash
67
70
 
68
71
  # Create a new Route.
69
72
  #
70
73
  # @param path [String] The raw route path (may include `:params` or `*splat`).
71
74
  # @param options [Hash] The options for the route.
72
75
  # @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.
76
+ # @option options [String] :to The controller#action string.
77
+ # @option options [String] :controller The controller name.
78
+ # @option options [String] :action The action name.
79
+ # @option options [String] :as The route name.
80
+ # @option options [Hash] :constraints The route constraints.
81
+ # @option options [Hash] :defaults The route defaults.
79
82
  def initialize(path, options = {})
83
+ @options = options
80
84
  @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
85
+ @name = @options[:as]
86
+ @constraints = (@options[:constraints] || {}).freeze
87
+ @defaults = (@options[:defaults] || {}).transform_keys(&:to_s).freeze
88
88
  @param_key_slots = [[nil, nil], [nil, nil]]
89
89
  @required_validated_once = false
90
90
 
91
+ setup_caches
92
+
91
93
  precompile_route_data
92
94
  validate_route!
93
95
  end
@@ -121,55 +123,32 @@ module RubyRoutes
121
123
 
122
124
  private
123
125
 
124
- # Set up HTTP methods from options.
126
+ # Split path into parts.
125
127
  #
126
- # @param options [Hash] The options for the route.
127
- # @return [void]
128
- def setup_methods(options)
129
- raw_http_methods = Array(options[:via] || :get)
130
- @methods = raw_http_methods.map { |method| normalize_http_method(method) }.freeze
131
- @methods_set = @methods.to_set.freeze
128
+ # @param path [String] The path to split.
129
+ # @return [Array<String>]
130
+ def split_path(path)
131
+ path.split('/').reject(&:empty?)
132
132
  end
133
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
-
162
- to.to_s.split('#', 2).last
163
- end
134
+ # Expose for testing / external callers.
135
+ public :extract_path_params_fast
164
136
 
165
137
  # Precompile route data for performance.
166
138
  #
167
139
  # @return [void]
168
140
  def precompile_route_data
141
+ raw_http_methods = Array(@options[:via] || :get)
142
+ @methods = raw_http_methods.map { |method| normalize_http_method(method) }.freeze
143
+ @methods_set = @methods.to_set.freeze
144
+
145
+ to_str = @options[:to].to_s
146
+ to_controller, to_action = to_str.split('#', 2)
147
+ @controller = @options[:controller] || to_controller
148
+ @action = @options[:action] || to_action
149
+
169
150
  @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
151
+
173
152
  initialize_validation_cache
174
153
  compile_segments
175
154
  compile_required_params