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.
- checksums.yaml +4 -4
- data/README.md +0 -23
- data/lib/ruby_routes/constant.rb +18 -3
- data/lib/ruby_routes/lru_strategies/hit_strategy.rb +1 -1
- data/lib/ruby_routes/node.rb +14 -87
- data/lib/ruby_routes/radix_tree/finder.rb +75 -47
- data/lib/ruby_routes/radix_tree/inserter.rb +2 -55
- data/lib/ruby_routes/radix_tree/traversal_strategy/base.rb +18 -0
- data/lib/ruby_routes/radix_tree/traversal_strategy/generic_loop.rb +25 -0
- data/lib/ruby_routes/radix_tree/traversal_strategy/unrolled.rb +45 -0
- data/lib/ruby_routes/radix_tree/traversal_strategy.rb +26 -0
- data/lib/ruby_routes/radix_tree.rb +12 -62
- data/lib/ruby_routes/route/check_helpers.rb +3 -3
- data/lib/ruby_routes/route/constraint_validator.rb +22 -1
- data/lib/ruby_routes/route/matcher.rb +11 -0
- data/lib/ruby_routes/route/param_support.rb +9 -8
- data/lib/ruby_routes/route/path_builder.rb +11 -6
- data/lib/ruby_routes/route/path_generation.rb +5 -1
- data/lib/ruby_routes/route/small_lru.rb +43 -2
- data/lib/ruby_routes/route/validation_helpers.rb +6 -36
- data/lib/ruby_routes/route.rb +35 -56
- data/lib/ruby_routes/route_set/cache_helpers.rb +29 -13
- data/lib/ruby_routes/route_set/collection_helpers.rb +8 -10
- data/lib/ruby_routes/route_set.rb +34 -59
- data/lib/ruby_routes/router/build_helpers.rb +1 -7
- data/lib/ruby_routes/router/builder.rb +12 -12
- data/lib/ruby_routes/router/http_helpers.rb +7 -48
- data/lib/ruby_routes/router/resource_helpers.rb +23 -37
- data/lib/ruby_routes/router/scope_helpers.rb +26 -14
- data/lib/ruby_routes/router.rb +28 -29
- data/lib/ruby_routes/segment.rb +3 -3
- data/lib/ruby_routes/segments/base_segment.rb +8 -0
- data/lib/ruby_routes/segments/static_segment.rb +3 -1
- data/lib/ruby_routes/strategies/base.rb +18 -0
- data/lib/ruby_routes/strategies/hash_based_strategy.rb +33 -0
- data/lib/ruby_routes/strategies/hybrid_strategy.rb +70 -0
- data/lib/ruby_routes/strategies/radix_tree_strategy.rb +24 -0
- data/lib/ruby_routes/strategies.rb +5 -0
- data/lib/ruby_routes/utility/key_builder_utility.rb +4 -26
- data/lib/ruby_routes/utility/method_utility.rb +11 -11
- data/lib/ruby_routes/utility/path_utility.rb +18 -7
- data/lib/ruby_routes/version.rb +1 -1
- data/lib/ruby_routes.rb +3 -1
- metadata +11 -2
- data/lib/ruby_routes/string_extensions.rb +0 -65
data/lib/ruby_routes/router.rb
CHANGED
@@ -6,6 +6,7 @@ require_relative 'router/http_helpers'
|
|
6
6
|
require_relative 'router/resource_helpers'
|
7
7
|
require_relative 'constant'
|
8
8
|
require_relative 'route_set'
|
9
|
+
|
9
10
|
module RubyRoutes
|
10
11
|
# RubyRoutes::Router
|
11
12
|
#
|
@@ -64,15 +65,28 @@ module RubyRoutes
|
|
64
65
|
|
65
66
|
# Initialize the router.
|
66
67
|
#
|
68
|
+
# @param strategy [Class] The matching strategy to use.
|
67
69
|
# @param definition_block [Proc] The block to define routes.
|
68
|
-
def initialize(&definition_block)
|
69
|
-
@route_set = RouteSet.new
|
70
|
+
def initialize(strategy: Strategies::HybridStrategy, &definition_block)
|
71
|
+
@route_set = RouteSet.new(strategy: strategy)
|
70
72
|
@route_utils = RubyRoutes::Utility::RouteUtility.new(@route_set)
|
71
73
|
@scope_stack = []
|
72
74
|
@concerns = {}
|
75
|
+
@frozen = false
|
73
76
|
instance_eval(&definition_block) if definition_block
|
74
77
|
end
|
75
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
|
+
|
76
90
|
# Build a finalized router.
|
77
91
|
#
|
78
92
|
# @param definition_block [Proc] The block to define routes.
|
@@ -88,12 +102,7 @@ module RubyRoutes
|
|
88
102
|
return self if @frozen
|
89
103
|
|
90
104
|
@frozen = true
|
91
|
-
|
92
|
-
@route_set.finalize!
|
93
|
-
else
|
94
|
-
@route_set.freeze
|
95
|
-
end
|
96
|
-
@route_utils.freeze if @route_utils.respond_to?(:freeze)
|
105
|
+
@route_set.freeze
|
97
106
|
@scope_stack.freeze
|
98
107
|
@concerns.freeze
|
99
108
|
self
|
@@ -155,9 +164,9 @@ module RubyRoutes
|
|
155
164
|
# @return [Router] self.
|
156
165
|
def namespace(namespace_name, options = {}, &block)
|
157
166
|
ensure_unfrozen!
|
158
|
-
|
159
|
-
|
160
|
-
|
167
|
+
scoped_options = { path: "/#{namespace_name}", module: namespace_name }.merge(options)
|
168
|
+
scope(scoped_options, &block)
|
169
|
+
self
|
161
170
|
end
|
162
171
|
|
163
172
|
# Define a scope.
|
@@ -169,26 +178,16 @@ module RubyRoutes
|
|
169
178
|
ensure_unfrozen!
|
170
179
|
scope_entry = options_or_path.is_a?(String) ? { path: options_or_path } : options_or_path
|
171
180
|
push_scope(scope_entry) { instance_eval(&block) if block }
|
181
|
+
self
|
172
182
|
end
|
173
183
|
|
174
|
-
#
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
push_scope(constraints: constraints_hash) { instance_eval(&block) if block }
|
182
|
-
end
|
183
|
-
|
184
|
-
# Define defaults.
|
185
|
-
#
|
186
|
-
# @param defaults_hash [Hash] The default values for the scope.
|
187
|
-
# @param block [Proc] The block for nested routes.
|
188
|
-
# @return [Router] self.
|
189
|
-
def defaults(defaults_hash = {}, &block)
|
190
|
-
ensure_unfrozen!
|
191
|
-
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
|
192
191
|
end
|
193
192
|
|
194
193
|
# ---- Concerns ----------------------------------------------------------
|
data/lib/ruby_routes/segment.rb
CHANGED
@@ -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
|
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(
|
34
|
-
segment_text =
|
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
|
@@ -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
|
58
|
-
|
56
|
+
method_key = http_method.freeze
|
57
|
+
path_key = request_path.to_s.freeze
|
59
58
|
bucket = @request_key_pool[method_key] ||= {}
|
60
|
-
|
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
|
-
|
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)
|
@@ -42,7 +42,7 @@ module RubyRoutes
|
|
42
42
|
# Now uses SmallLru for LRU eviction instead of simple clearing.
|
43
43
|
#
|
44
44
|
# @return [SmallLru]
|
45
|
-
METHOD_CACHE = RubyRoutes::Route::SmallLru.new(RubyRoutes::Constant::
|
45
|
+
METHOD_CACHE = RubyRoutes::Route::SmallLru.new(RubyRoutes::Constant::CACHE_SIZE)
|
46
46
|
|
47
47
|
# Normalize an HTTP method‑like input to a canonical uppercase `String`.
|
48
48
|
#
|
@@ -69,16 +69,20 @@ module RubyRoutes
|
|
69
69
|
|
70
70
|
private
|
71
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
|
+
|
72
80
|
# Normalize a `String` HTTP method.
|
73
81
|
#
|
74
82
|
# @param method_input [String] The HTTP method input.
|
75
83
|
# @return [String] The normalized HTTP method.
|
76
84
|
def normalize_string_method(method_input)
|
77
|
-
|
78
|
-
|
79
|
-
# Use SmallLru for LRU eviction, freeze key to prevent mutation
|
80
|
-
key = method_input.dup.freeze
|
81
|
-
METHOD_CACHE.get(key) || METHOD_CACHE.set(key, ascii_upcase(method_input.dup).freeze)
|
85
|
+
cache_normalized_method(method_input)
|
82
86
|
end
|
83
87
|
|
84
88
|
# Normalize a `Symbol` HTTP method.
|
@@ -98,11 +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
|
-
|
102
|
-
|
103
|
-
# Use SmallLru for LRU eviction, freeze key to prevent mutation
|
104
|
-
key = coerced.dup.freeze
|
105
|
-
METHOD_CACHE.get(key) || METHOD_CACHE.set(key, ascii_upcase(coerced.dup).freeze)
|
105
|
+
cache_normalized_method(coerced)
|
106
106
|
end
|
107
107
|
|
108
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
60
|
-
|
60
|
+
return [] if raw_path == '/' || raw_path.empty?
|
61
|
+
|
62
|
+
# Strip query strings and fragments
|
63
|
+
path = raw_path.split(/[?#]/).first
|
61
64
|
|
62
|
-
|
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.
|
data/lib/ruby_routes/version.rb
CHANGED
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
|
+
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/
|
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
|