ruby_routes 2.4.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -23
  3. data/lib/ruby_routes/constant.rb +20 -3
  4. data/lib/ruby_routes/lru_strategies/hit_strategy.rb +1 -1
  5. data/lib/ruby_routes/node.rb +15 -87
  6. data/lib/ruby_routes/radix_tree/finder.rb +79 -52
  7. data/lib/ruby_routes/radix_tree/inserter.rb +2 -54
  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 +24 -1
  15. data/lib/ruby_routes/route/matcher.rb +11 -0
  16. data/lib/ruby_routes/route/param_support.rb +9 -7
  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/warning_helpers.rb +7 -5
  22. data/lib/ruby_routes/route.rb +35 -55
  23. data/lib/ruby_routes/route_set/cache_helpers.rb +32 -13
  24. data/lib/ruby_routes/route_set/collection_helpers.rb +15 -14
  25. data/lib/ruby_routes/route_set.rb +32 -69
  26. data/lib/ruby_routes/router/build_helpers.rb +1 -1
  27. data/lib/ruby_routes/router/builder.rb +16 -33
  28. data/lib/ruby_routes/router/http_helpers.rb +13 -49
  29. data/lib/ruby_routes/router/resource_helpers.rb +26 -39
  30. data/lib/ruby_routes/router/scope_helpers.rb +26 -14
  31. data/lib/ruby_routes/router.rb +41 -24
  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/static_segment.rb +3 -1
  35. data/lib/ruby_routes/strategies/base.rb +18 -0
  36. data/lib/ruby_routes/strategies/hash_based_strategy.rb +33 -0
  37. data/lib/ruby_routes/strategies/hybrid_strategy.rb +70 -0
  38. data/lib/ruby_routes/strategies/radix_tree_strategy.rb +24 -0
  39. data/lib/ruby_routes/strategies.rb +5 -0
  40. data/lib/ruby_routes/utility/key_builder_utility.rb +4 -26
  41. data/lib/ruby_routes/utility/method_utility.rb +18 -17
  42. data/lib/ruby_routes/utility/path_utility.rb +18 -7
  43. data/lib/ruby_routes/version.rb +1 -1
  44. data/lib/ruby_routes.rb +3 -1
  45. metadata +11 -2
  46. data/lib/ruby_routes/string_extensions.rb +0 -65
@@ -3,8 +3,10 @@
3
3
  require_relative 'utility/inflector_utility'
4
4
  require_relative 'utility/route_utility'
5
5
  require_relative 'router/http_helpers'
6
+ require_relative 'router/resource_helpers'
6
7
  require_relative 'constant'
7
8
  require_relative 'route_set'
9
+
8
10
  module RubyRoutes
9
11
  # RubyRoutes::Router
10
12
  #
@@ -63,15 +65,28 @@ module RubyRoutes
63
65
 
64
66
  # Initialize the router.
65
67
  #
68
+ # @param strategy [Class] The matching strategy to use.
66
69
  # @param definition_block [Proc] The block to define routes.
67
- def initialize(&definition_block)
68
- @route_set = RouteSet.new
70
+ def initialize(strategy: Strategies::HybridStrategy, &definition_block)
71
+ @route_set = RouteSet.new(strategy: strategy)
69
72
  @route_utils = RubyRoutes::Utility::RouteUtility.new(@route_set)
70
73
  @scope_stack = []
71
74
  @concerns = {}
75
+ @frozen = false
72
76
  instance_eval(&definition_block) if definition_block
73
77
  end
74
78
 
79
+ # Add a route to the route set.
80
+ #
81
+ # @param path [String] The route path.
82
+ # @param options [Hash] The route options.
83
+ # @return [void]
84
+ def add_route(path, options = {})
85
+ ensure_unfrozen!
86
+ scoped_options = apply_scope(path, options)
87
+ @route_utils.define(scoped_options[:path], scoped_options.except(:path))
88
+ end
89
+
75
90
  # Build a finalized router.
76
91
  #
77
92
  # @param definition_block [Proc] The block to define routes.
@@ -87,11 +102,11 @@ module RubyRoutes
87
102
  return self if @frozen
88
103
 
89
104
  @frozen = true
105
+ @route_set.freeze
90
106
  @scope_stack.freeze
91
107
  @concerns.freeze
92
108
  self
93
109
  end
94
-
95
110
  # Check if the router is frozen.
96
111
  #
97
112
  # @return [Boolean] `true` if the router is frozen, `false` otherwise.
@@ -99,12 +114,17 @@ module RubyRoutes
99
114
  !!@frozen
100
115
  end
101
116
 
102
- # Define a root route.
117
+ # Define the root route.
103
118
  #
104
119
  # @param options [Hash] The options for the root route.
105
120
  # @return [Router] self.
106
121
  def root(options = {})
107
- add_route('/', build_route_options(options, :get))
122
+ ensure_unfrozen!
123
+ if options[:to]
124
+ add_route('/', via: [:get], **options)
125
+ else
126
+ add_route('/', controller: 'root', action: :index, via: [:get], **options)
127
+ end
108
128
  self
109
129
  end
110
130
 
@@ -117,6 +137,7 @@ module RubyRoutes
117
137
  # @param nested_block [Proc] The block for nested routes.
118
138
  # @return [Router] self.
119
139
  def resources(resource_name, options = {}, &nested_block)
140
+ ensure_unfrozen!
120
141
  define_resource_routes(resource_name, options, &nested_block)
121
142
  self
122
143
  end
@@ -127,6 +148,7 @@ module RubyRoutes
127
148
  # @param options [Hash] The options for the resource.
128
149
  # @return [Router] self.
129
150
  def resource(resource_name, options = {})
151
+ ensure_unfrozen!
130
152
  singular = RubyRoutes::Utility::InflectorUtility.singularize(resource_name.to_s)
131
153
  controller = options[:controller] || singular
132
154
  define_singular_routes(singular, controller, options)
@@ -141,9 +163,10 @@ module RubyRoutes
141
163
  # @param block [Proc] The block for nested routes.
142
164
  # @return [Router] self.
143
165
  def namespace(namespace_name, options = {}, &block)
144
- push_scope({ path: "/#{namespace_name}", module: namespace_name }.merge(options)) do
145
- instance_eval(&block) if block
146
- end
166
+ ensure_unfrozen!
167
+ scoped_options = { path: "/#{namespace_name}", module: namespace_name }.merge(options)
168
+ scope(scoped_options, &block)
169
+ self
147
170
  end
148
171
 
149
172
  # Define a scope.
@@ -152,26 +175,19 @@ module RubyRoutes
152
175
  # @param block [Proc] The block for nested routes.
153
176
  # @return [Router] self.
154
177
  def scope(options_or_path = {}, &block)
178
+ ensure_unfrozen!
155
179
  scope_entry = options_or_path.is_a?(String) ? { path: options_or_path } : options_or_path
156
180
  push_scope(scope_entry) { instance_eval(&block) if block }
181
+ self
157
182
  end
158
183
 
159
- # Define constraints.
160
- #
161
- # @param constraints_hash [Hash] The constraints for the scope.
162
- # @param block [Proc] The block for nested routes.
163
- # @return [Router] self.
164
- def constraints(constraints_hash = {}, &block)
165
- push_scope(constraints: constraints_hash) { instance_eval(&block) if block }
166
- end
167
-
168
- # Define defaults.
169
- #
170
- # @param defaults_hash [Hash] The default values for the scope.
171
- # @param block [Proc] The block for nested routes.
172
- # @return [Router] self.
173
- def defaults(defaults_hash = {}, &block)
174
- push_scope(defaults: defaults_hash) { instance_eval(&block) if block }
184
+ # Metaprogram `constraints` and `defaults` for DRYness.
185
+ %i[constraints defaults].each do |scope_type|
186
+ define_method(scope_type) do |options_hash = {}, &block|
187
+ ensure_unfrozen!
188
+ push_scope(scope_type => options_hash) { instance_eval(&block) if block }
189
+ self
190
+ end
175
191
  end
176
192
 
177
193
  # ---- Concerns ----------------------------------------------------------
@@ -192,6 +208,7 @@ module RubyRoutes
192
208
  # @param block [Proc] The block for additional routes.
193
209
  # @return [void]
194
210
  def concerns(*concern_names, &block)
211
+ ensure_unfrozen!
195
212
  concern_names.each do |name|
196
213
  concern_block = @concerns[name]
197
214
  raise "Concern '#{name}' not found" unless concern_block
@@ -23,15 +23,15 @@ module RubyRoutes
23
23
  class Segment
24
24
  # Build an appropriate segment instance for the provided token.
25
25
  #
26
- # @param text [String, Symbol, #to_s] raw segment token
26
+ # @param segment_token [String, Symbol, #to_s] raw segment token
27
27
  # @return [RubyRoutes::Segments::BaseSegment]
28
28
  #
29
29
  # @example
30
30
  # Segment.for(":id") # => DynamicSegment
31
31
  # Segment.for("*files") # => WildcardSegment
32
32
  # Segment.for("users") # => StaticSegment
33
- def self.for(text)
34
- segment_text = text.to_s
33
+ def self.for(segment_token)
34
+ segment_text = segment_token.to_s
35
35
  segment_key = segment_text.empty? ? :default : segment_text.getbyte(0)
36
36
  segment_class = RubyRoutes::Constant::SEGMENTS[segment_key] || RubyRoutes::Constant::SEGMENTS[:default]
37
37
  segment_class.new(segment_text)
@@ -21,6 +21,14 @@ module RubyRoutes
21
21
  # @param raw_segment_text [String, Symbol, nil]
22
22
  def initialize(raw_segment_text = nil)
23
23
  @raw_text = raw_segment_text.to_s if raw_segment_text
24
+ @param_name = nil
25
+ end
26
+
27
+ # Get the parameter name for this segment (if any).
28
+ #
29
+ # @return [String, nil]
30
+ def param_name
31
+ @param_name
24
32
  end
25
33
 
26
34
  # Indicates whether this segment is a wildcard (greedy) segment.
@@ -23,10 +23,12 @@ module RubyRoutes
23
23
  #
24
24
  # @api internal
25
25
  class StaticSegment < BaseSegment
26
+ attr_reader :literal_text
27
+
26
28
  # @param raw_segment_text [String] literal segment token
27
29
  def initialize(raw_segment_text)
28
30
  super(raw_segment_text)
29
- @literal_text = raw_segment_text
31
+ @literal_text = raw_segment_text.freeze
30
32
  end
31
33
 
32
34
  # Ensure a static child node for this literal under +parent_node+.
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyRoutes
4
+ module Strategies
5
+ # Base
6
+ #
7
+ # Defines the interface for matching strategies.
8
+ module Base
9
+ def add(route)
10
+ raise NotImplementedError, "#{self.class.name} must implement #add"
11
+ end
12
+
13
+ def find(path, http_method)
14
+ raise NotImplementedError, "#{self.class.name} must implement #find"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module RubyRoutes
6
+ module Strategies
7
+ # HashBasedStrategy
8
+ #
9
+ # A simple hash-based lookup strategy for route matching.
10
+ class HashBasedStrategy
11
+ include Base
12
+
13
+ def initialize
14
+ @routes = {}
15
+ end
16
+
17
+ def add(route)
18
+ route.methods.each do |method|
19
+ key = "#{method.upcase}::#{route.path&.downcase}"
20
+ @routes[key] = route
21
+ end
22
+ end
23
+
24
+ def find(path, http_method)
25
+ key = "#{http_method.upcase}::#{path&.downcase}"
26
+ route = @routes[key]
27
+ return nil unless route
28
+
29
+ [route, {}]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+ require_relative '../radix_tree'
5
+
6
+ module RubyRoutes
7
+ module Strategies
8
+ # HybridStrategy: Combines hash-based lookup for static routes with
9
+ # radix tree lookup for dynamic routes.
10
+ #
11
+ # Performance optimization:
12
+ # - Static routes: O(1) hash lookup
13
+ # - Dynamic routes: O(path length) radix tree traversal
14
+ # - Automatic classification based on route pattern
15
+ class HybridStrategy
16
+ include Base
17
+
18
+ def initialize
19
+ @static_routes = {}
20
+ @dynamic_routes = RadixTree.new
21
+ end
22
+
23
+ # Add a route to the appropriate storage based on whether it's static or dynamic
24
+ #
25
+ # @param route [Route] The route to add
26
+ def add(route)
27
+ if static_route?(route.path)
28
+ @static_routes[route.path] ||= {}
29
+ route.methods.each do |method|
30
+ @static_routes[route.path][method] = route
31
+ end
32
+ else
33
+ # Extract path, methods, and handler from route for RadixTree
34
+ @dynamic_routes.add(route.path, route.methods, route)
35
+ end
36
+ end
37
+
38
+ # Find a route for the given path and method
39
+ #
40
+ # @param path [String] The request path
41
+ # @param method [String] The HTTP method
42
+ # @return [Array<Route, Hash>, nil] [route, params] or nil if not found
43
+ def find(path, method)
44
+ # Try static routes first (fastest path)
45
+ if @static_routes.key?(path) && @static_routes[path].key?(method)
46
+ route = @static_routes[path][method]
47
+ return [route, {}]
48
+ end
49
+
50
+ # Fall back to dynamic routes
51
+ result = @dynamic_routes.find(path, method)
52
+
53
+ # RadixTree returns [nil, {}] when no route found, convert to nil
54
+ return nil if result && result.first.nil?
55
+
56
+ result
57
+ end
58
+
59
+ private
60
+
61
+ # Determine if a route path is static (no parameters)
62
+ #
63
+ # @param path [String] The route path
64
+ # @return [Boolean] true if static, false if dynamic
65
+ def static_route?(path)
66
+ !path.include?(':') && !path.include?('*')
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../radix_tree'
4
+
5
+ module RubyRoutes
6
+ module Strategies
7
+ # RadixTreeStrategy
8
+ #
9
+ # Encapsulates RadixTree-based route matching.
10
+ class RadixTreeStrategy
11
+ def initialize
12
+ @radix_tree = RadixTree.new
13
+ end
14
+
15
+ def add(route)
16
+ @radix_tree.add(route.path, route.methods, route)
17
+ end
18
+
19
+ def find(path, http_method)
20
+ @radix_tree.find(path, http_method)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'strategies/base'
4
+ require_relative 'strategies/radix_tree_strategy'
5
+ require_relative 'strategies/hash_based_strategy'
@@ -49,40 +49,18 @@ module RubyRoutes
49
49
  # - Records it in the nested pool.
50
50
  # - Tracks insertion in a fixed ring; when full, overwrites oldest.
51
51
  #
52
- # @param http_method [String] The HTTP method (e.g., "GET").
53
52
  # @param request_path [String] The request path (e.g., "/users").
54
53
  # @return [String] A frozen canonical key.
55
54
  def fetch_request_key(http_method, request_path)
56
55
  @mutex.synchronize do
57
- method_key, path_key = prepare_keys(http_method, request_path)
58
-
56
+ method_key = http_method.freeze
57
+ path_key = request_path.to_s.freeze
59
58
  bucket = @request_key_pool[method_key] ||= {}
60
- return bucket[path_key] if bucket[path_key]
61
-
62
- handle_cache_miss(bucket, method_key, path_key)
59
+ bucket[path_key] || create_and_cache_key(bucket, method_key, path_key)
63
60
  end
64
61
  end
65
62
 
66
- private
67
-
68
- # Prepare keys by freezing them if necessary.
69
- #
70
- # @param http_method [String] The HTTP method.
71
- # @param request_path [String] The request path.
72
- # @return [Array<String>] An array containing the frozen method and path keys.
73
- def prepare_keys(http_method, request_path)
74
- method_key = http_method.frozen? ? http_method : http_method.dup.freeze
75
- path_key = request_path.frozen? ? request_path : request_path.dup.freeze
76
- [method_key, path_key]
77
- end
78
-
79
- # Handle a cache miss by creating a composite key and updating the ring buffer.
80
- #
81
- # @param bucket [Hash] The bucket for the method key.
82
- # @param method_key [String] The HTTP method key.
83
- # @param path_key [String] The path key.
84
- # @return [String] The composite key.
85
- def handle_cache_miss(bucket, method_key, path_key)
63
+ def create_and_cache_key(bucket, method_key, path_key)
86
64
  composite = "#{method_key}:#{path_key}".freeze
87
65
  bucket[path_key] = composite
88
66
  handle_ring_buffer(method_key, path_key)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../constant'
4
+ require_relative '../route/small_lru'
4
5
 
5
6
  module RubyRoutes
6
7
  module Utility
@@ -38,15 +39,10 @@ module RubyRoutes
38
39
  }.freeze
39
40
 
40
41
  # Cache for non‑predefined or previously seen method tokens.
41
- # Keys: original `String` or `Symbol`
42
- # Values: frozen uppercase `String`
42
+ # Now uses SmallLru for LRU eviction instead of simple clearing.
43
43
  #
44
- # Note: This is intentionally mutable for caching purposes.
45
- #
46
- # @return [Hash{(String,Symbol) => String}]
47
- # rubocop:disable Style/MutableConstant
48
- METHOD_CACHE = {} # Intentionally mutable for caching
49
- # rubocop:enable Style/MutableConstant
44
+ # @return [SmallLru]
45
+ METHOD_CACHE = RubyRoutes::Route::SmallLru.new(RubyRoutes::Constant::CACHE_SIZE)
50
46
 
51
47
  # Normalize an HTTP method‑like input to a canonical uppercase `String`.
52
48
  #
@@ -73,15 +69,20 @@ module RubyRoutes
73
69
 
74
70
  private
75
71
 
72
+ def cache_normalized_method(input_string)
73
+ return input_string if already_upper_ascii?(input_string)
74
+
75
+ # Use SmallLru for LRU eviction, freeze key to prevent mutation
76
+ key = input_string.dup.freeze
77
+ METHOD_CACHE.get(key) || METHOD_CACHE.set(key, ascii_upcase(input_string.dup).freeze)
78
+ end
79
+
76
80
  # Normalize a `String` HTTP method.
77
81
  #
78
82
  # @param method_input [String] The HTTP method input.
79
83
  # @return [String] The normalized HTTP method.
80
84
  def normalize_string_method(method_input)
81
- return method_input if already_upper_ascii?(method_input)
82
-
83
- key = method_input.dup
84
- METHOD_CACHE[key] ||= ascii_upcase(key).freeze
85
+ cache_normalized_method(method_input)
85
86
  end
86
87
 
87
88
  # Normalize a `Symbol` HTTP method.
@@ -89,7 +90,10 @@ module RubyRoutes
89
90
  # @param method_input [Symbol] The HTTP method input.
90
91
  # @return [String] The normalized HTTP method.
91
92
  def normalize_symbol_method(method_input)
92
- SYMBOL_MAP[method_input] || (METHOD_CACHE[method_input] ||= ascii_upcase(method_input.to_s).freeze)
93
+ SYMBOL_MAP[method_input] || begin
94
+ key = method_input.to_s.freeze
95
+ METHOD_CACHE.get(key) || METHOD_CACHE.set(key, ascii_upcase(method_input.to_s).freeze)
96
+ end
93
97
  end
94
98
 
95
99
  # Normalize an arbitrary HTTP method input.
@@ -98,10 +102,7 @@ module RubyRoutes
98
102
  # @return [String] The normalized HTTP method.
99
103
  def normalize_other_method(method_input)
100
104
  coerced = method_input.to_s
101
- return coerced if already_upper_ascii?(coerced)
102
-
103
- key = coerced.dup
104
- METHOD_CACHE[key] ||= ascii_upcase(key).freeze
105
+ cache_normalized_method(coerced)
105
106
  end
106
107
 
107
108
  # Determine if a `String` consists solely of uppercase ASCII (`A–Z`) or non‑letters.
@@ -32,10 +32,11 @@ module RubyRoutes
32
32
  # normalize_path('/users') # => "/users"
33
33
  # normalize_path('/') # => "/"
34
34
  def normalize_path(raw_path)
35
- normalized_path = raw_path.to_s
36
- normalized_path = "/#{normalized_path}" unless normalized_path.start_with?('/')
37
- normalized_path = normalized_path[0..-2] if normalized_path.length > 1 && normalized_path.end_with?('/')
38
- normalized_path
35
+ return '/' if raw_path.nil? || raw_path.empty?
36
+
37
+ path = raw_path.start_with?('/') ? raw_path : "/#{raw_path}"
38
+ path = path.chomp('/') unless path == '/'
39
+ path
39
40
  end
40
41
 
41
42
  # Normalize HTTP method to uppercase String (fast path).
@@ -56,10 +57,20 @@ module RubyRoutes
56
57
  # split_path('/users/123?x=1') # => ["users", "123"]
57
58
  # split_path('/') # => []
58
59
  def split_path(raw_path)
59
- path_without_query = raw_path.to_s.split(/[?#]/, 2).first
60
- return [] if path_without_query.nil? || path_without_query.empty?
60
+ return [] if raw_path == '/' || raw_path.empty?
61
+
62
+ # Strip query strings and fragments
63
+ path = raw_path.split(/[?#]/).first
61
64
 
62
- path_without_query.split('/').reject(&:empty?)
65
+ # Optimized trimming: avoid string allocations when possible
66
+ start_idx = path.start_with?('/') ? 1 : 0
67
+ end_idx = path.end_with?('/') ? -2 : -1
68
+
69
+ if start_idx == 0 && end_idx == -1
70
+ path.split('/').reject(&:empty?)
71
+ else
72
+ path[start_idx..end_idx].split('/').reject(&:empty?)
73
+ end
63
74
  end
64
75
 
65
76
  # Join path parts into a normalized absolute path.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyRoutes
4
- VERSION = '2.4.0'
4
+ VERSION = '2.6.0'
5
5
  end
data/lib/ruby_routes.rb CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  require_relative 'ruby_routes/version'
4
4
  require_relative 'ruby_routes/constant'
5
- require_relative 'ruby_routes/string_extensions'
6
5
  require_relative 'ruby_routes/route'
7
6
  require_relative 'ruby_routes/route_set'
8
7
  require_relative 'ruby_routes/url_helpers'
@@ -10,6 +9,9 @@ require_relative 'ruby_routes/router'
10
9
  require_relative 'ruby_routes/radix_tree'
11
10
  require_relative 'ruby_routes/node'
12
11
  require_relative 'ruby_routes/router/builder'
12
+ require_relative 'ruby_routes/strategies/base'
13
+ require_relative 'ruby_routes/strategies/hash_based_strategy'
14
+ require_relative 'ruby_routes/strategies/hybrid_strategy'
13
15
 
14
16
  # RubyRoutes
15
17
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_routes
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yosef Benny Widyokarsono
@@ -83,9 +83,14 @@ files:
83
83
  - lib/ruby_routes/radix_tree.rb
84
84
  - lib/ruby_routes/radix_tree/finder.rb
85
85
  - lib/ruby_routes/radix_tree/inserter.rb
86
+ - lib/ruby_routes/radix_tree/traversal_strategy.rb
87
+ - lib/ruby_routes/radix_tree/traversal_strategy/base.rb
88
+ - lib/ruby_routes/radix_tree/traversal_strategy/generic_loop.rb
89
+ - lib/ruby_routes/radix_tree/traversal_strategy/unrolled.rb
86
90
  - lib/ruby_routes/route.rb
87
91
  - lib/ruby_routes/route/check_helpers.rb
88
92
  - lib/ruby_routes/route/constraint_validator.rb
93
+ - lib/ruby_routes/route/matcher.rb
89
94
  - lib/ruby_routes/route/param_support.rb
90
95
  - lib/ruby_routes/route/path_builder.rb
91
96
  - lib/ruby_routes/route/path_generation.rb
@@ -108,7 +113,11 @@ files:
108
113
  - lib/ruby_routes/segments/dynamic_segment.rb
109
114
  - lib/ruby_routes/segments/static_segment.rb
110
115
  - lib/ruby_routes/segments/wildcard_segment.rb
111
- - lib/ruby_routes/string_extensions.rb
116
+ - lib/ruby_routes/strategies.rb
117
+ - lib/ruby_routes/strategies/base.rb
118
+ - lib/ruby_routes/strategies/hash_based_strategy.rb
119
+ - lib/ruby_routes/strategies/hybrid_strategy.rb
120
+ - lib/ruby_routes/strategies/radix_tree_strategy.rb
112
121
  - lib/ruby_routes/url_helpers.rb
113
122
  - lib/ruby_routes/utility/inflector_utility.rb
114
123
  - lib/ruby_routes/utility/key_builder_utility.rb
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Minimal String inflection helpers.
4
- #
5
- # @note This is a very small, intentionally naive English inflector
6
- # covering only a few common pluralization patterns used inside the
7
- # routing DSL (e.g., resources / resource helpers). It is NOT a full
8
- # replacement for ActiveSupport::Inflector and should not be relied on
9
- # for general linguistic correctness.
10
- #
11
- # @note Supported patterns:
12
- # - Singularize:
13
- # * words ending in "ies" -> "y" (companies -> company)
14
- # * words ending in "s" -> strip trailing "s" (users -> user)
15
- # - Pluralize:
16
- # * words ending in "y" -> replace with "ies" (company -> companies)
17
- # * words ending in sh/ch/x -> append "es" (box -> boxes)
18
- # * words ending in "z" -> append "zes" (quiz -> quizzes) (simplified)
19
- # * words ending in "s" -> unchanged
20
- # * default -> append "s"
21
- #
22
- # @note Limitations:
23
- # - Does not handle irregular forms (person/people, child/children, etc.).
24
- # - Simplified handling of "z" endings (adds "zes" instead of "zzes").
25
- # - Case‑sensitive (expects lowercase ASCII).
26
- #
27
- # @api internal
28
- class String
29
- # Convert a plural form to a simplistic singular.
30
- #
31
- # @example Singularize a word
32
- # "companies".singularize # => "company"
33
- # "users".singularize # => "user"
34
- # "box".singularize # => "box" (unchanged)
35
- #
36
- # @return [String] Singularized form (may be the same object if no change is needed).
37
- def singularize
38
- case self
39
- when /ies$/
40
- sub(/ies$/, 'y')
41
- when /s$/
42
- sub(/s$/, '')
43
- else
44
- self
45
- end
46
- end
47
-
48
- # Convert a singular form to a simplistic plural.
49
- #
50
- # @example Pluralize a word
51
- # "company".pluralize # => "companies"
52
- # "box".pluralize # => "boxes"
53
- # "quiz".pluralize # => "quizzes"
54
- # "user".pluralize # => "users"
55
- #
56
- # @return [String] Pluralized form.
57
- def pluralize
58
- return self if end_with?('s')
59
- return sub(/y$/, 'ies') if end_with?('y')
60
- return "#{self}es" if match?(/sh$|ch$|x$/)
61
- return "#{self}zes" if end_with?('z')
62
-
63
- "#{self}s"
64
- end
65
- end