ruby_routes 2.4.0 → 2.5.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/lib/ruby_routes/constant.rb +4 -2
- data/lib/ruby_routes/node.rb +1 -0
- data/lib/ruby_routes/radix_tree/finder.rb +8 -9
- data/lib/ruby_routes/radix_tree/inserter.rb +1 -0
- data/lib/ruby_routes/route/constraint_validator.rb +2 -0
- data/lib/ruby_routes/route/param_support.rb +1 -0
- data/lib/ruby_routes/route/warning_helpers.rb +7 -5
- data/lib/ruby_routes/route.rb +3 -2
- data/lib/ruby_routes/route_set/cache_helpers.rb +7 -4
- data/lib/ruby_routes/route_set/collection_helpers.rb +12 -9
- data/lib/ruby_routes/route_set.rb +0 -12
- data/lib/ruby_routes/router/build_helpers.rb +6 -0
- data/lib/ruby_routes/router/builder.rb +4 -21
- data/lib/ruby_routes/router/http_helpers.rb +6 -1
- data/lib/ruby_routes/router/resource_helpers.rb +3 -2
- data/lib/ruby_routes/router.rb +21 -3
- data/lib/ruby_routes/utility/method_utility.rb +14 -13
- data/lib/ruby_routes/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07fa3c9d3d13ac8b73c6f4775871cd5ad6c3cd7c5d0e571797b83fb0dea72189
|
4
|
+
data.tar.gz: 2684722163772fe4489d4e8c32eaff036002c5b58e05be68e25e9a0c59cd680b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfc84b5cfa67c1da89ab8067ad01c8706c81717611969c287a4a536055fe1ee1b4ac80b791248c05dddb090548f1e29006889d2d7f5e91b9058349b9f9a8b317
|
7
|
+
data.tar.gz: b2ab1c86ca6838a4164621987c813b7320e3f042d1666576e00c3acf48576204c837fd13582ed42f2cf896334b9e1683c8ceab8868c03b7f9a482dcb808a5052
|
data/lib/ruby_routes/constant.rb
CHANGED
@@ -107,6 +107,8 @@ module RubyRoutes
|
|
107
107
|
# @return [Integer]
|
108
108
|
QUERY_CACHE_SIZE = 128
|
109
109
|
|
110
|
+
METHOD_CACHE_MAX_SIZE = 1000
|
111
|
+
|
110
112
|
# HTTP method constants.
|
111
113
|
HTTP_GET = 'GET'
|
112
114
|
HTTP_POST = 'POST'
|
@@ -146,7 +148,7 @@ module RubyRoutes
|
|
146
148
|
# Default result for no traversal match.
|
147
149
|
#
|
148
150
|
# @return [Array]
|
149
|
-
NO_TRAVERSAL_RESULT = [nil, false,
|
151
|
+
NO_TRAVERSAL_RESULT = [nil, false, EMPTY_HASH].freeze
|
150
152
|
|
151
153
|
# Built-in validators for constraints.
|
152
154
|
#
|
@@ -171,7 +173,7 @@ module RubyRoutes
|
|
171
173
|
segment_string = raw.to_s
|
172
174
|
dispatch_key = segment_string.empty? ? :default : segment_string.getbyte(0)
|
173
175
|
factory = DESCRIPTOR_FACTORIES[dispatch_key] || DESCRIPTOR_FACTORIES[:default]
|
174
|
-
factory.call(segment_string)
|
176
|
+
factory.call(segment_string).freeze
|
175
177
|
end
|
176
178
|
end
|
177
179
|
end
|
data/lib/ruby_routes/node.rb
CHANGED
@@ -96,7 +96,10 @@ module RubyRoutes
|
|
96
96
|
# @return [Array] [next_node, stop_traversal, segment_captured]
|
97
97
|
def traverse_for_segment(node, segment, index, segments, params, captured_params)
|
98
98
|
next_node, stop, segment_captured = node.traverse_for(segment, index, segments, params)
|
99
|
-
|
99
|
+
if segment_captured
|
100
|
+
params.merge!(segment_captured) # Merge into running params hash at each step
|
101
|
+
captured_params.merge!(segment_captured) # Keep for best candidate consistency
|
102
|
+
end
|
100
103
|
[next_node, stop]
|
101
104
|
end
|
102
105
|
|
@@ -162,9 +165,7 @@ module RubyRoutes
|
|
162
165
|
# @param params [Hash] parameters hash
|
163
166
|
# @param captured_params [Hash] captured parameters from traversal
|
164
167
|
# @return [Array] [handler, params] or [nil, params]
|
165
|
-
|
166
|
-
finalize_match(state[:best_node], method, state[:best_params], state[:best_captured])
|
167
|
-
end
|
168
|
+
|
168
169
|
|
169
170
|
# Common method to finalize a match attempt.
|
170
171
|
# Assumes the node is already validated as an endpoint.
|
@@ -175,20 +176,18 @@ module RubyRoutes
|
|
175
176
|
# @param captured_params [Hash] captured parameters from traversal
|
176
177
|
# @return [Array] [handler, params] or [nil, params]
|
177
178
|
def finalize_match(node, method, params, captured_params)
|
179
|
+
# Apply captured params once at the beginning
|
180
|
+
apply_captured_params(params, captured_params)
|
181
|
+
|
178
182
|
if node && endpoint_with_method?(node, method)
|
179
183
|
handler = node.handlers[method]
|
180
|
-
# Apply captured params before constraint validation
|
181
|
-
apply_captured_params(params, captured_params)
|
182
184
|
if check_constraints(handler, params)
|
183
185
|
return [handler, params]
|
184
186
|
end
|
185
187
|
end
|
186
188
|
# For non-matching paths, return nil
|
187
|
-
apply_captured_params(params, captured_params)
|
188
189
|
[nil, params]
|
189
190
|
end
|
190
|
-
|
191
|
-
# Handles matching for the root path.
|
192
191
|
#
|
193
192
|
# @param method [String] HTTP method
|
194
193
|
# @param params_out [Hash] parameters hash
|
@@ -46,6 +46,7 @@ module RubyRoutes
|
|
46
46
|
# @return [Node] the dynamic child node
|
47
47
|
def handle_dynamic(current_node, token)
|
48
48
|
param_name = token[1..]
|
49
|
+
raise ArgumentError, "Dynamic parameter name cannot be empty" if param_name.nil? || param_name.empty?
|
49
50
|
current_node.dynamic_child ||= build_param_node(param_name)
|
50
51
|
current_node.dynamic_child
|
51
52
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'set'
|
4
|
+
require 'thread'
|
4
5
|
|
5
6
|
module RubyRoutes
|
6
7
|
class Route
|
@@ -19,13 +20,14 @@ module RubyRoutes
|
|
19
20
|
# @param param [String, Symbol] The parameter name for which the warning
|
20
21
|
# is being emitted.
|
21
22
|
# @return [void]
|
23
|
+
|
22
24
|
def warn_proc_constraint_deprecation(param)
|
23
25
|
key = param.to_sym
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
@warnings_mutex ||= Mutex.new
|
27
|
+
@warnings_mutex.synchronize do
|
28
|
+
@proc_warnings_shown ||= Set.new
|
29
|
+
warn_proc_warning(key) if @proc_warnings_shown.add?(key)
|
30
|
+
end
|
29
31
|
end
|
30
32
|
|
31
33
|
# Warn about `Proc` constraint deprecation.
|
data/lib/ruby_routes/route.rb
CHANGED
@@ -83,7 +83,7 @@ module RubyRoutes
|
|
83
83
|
setup_controller_and_action(options)
|
84
84
|
|
85
85
|
@name = options[:as]
|
86
|
-
@constraints = options[:constraints] || {}
|
86
|
+
@constraints = (options[:constraints] || {}).freeze
|
87
87
|
@defaults = (options[:defaults] || {}).transform_keys(&:to_s).freeze
|
88
88
|
@param_key_slots = [[nil, nil], [nil, nil]]
|
89
89
|
@required_validated_once = false
|
@@ -145,8 +145,9 @@ module RubyRoutes
|
|
145
145
|
# @param options [Hash] The options for the route.
|
146
146
|
# @return [String, nil] The inferred controller name.
|
147
147
|
def extract_controller(options)
|
148
|
+
return options[:controller] if options[:controller]
|
148
149
|
to = options[:to]
|
149
|
-
return
|
150
|
+
return nil unless to
|
150
151
|
|
151
152
|
to.to_s.split('#', 2).first
|
152
153
|
end
|
@@ -64,12 +64,15 @@ module RubyRoutes
|
|
64
64
|
# @param entry [Hash] The cache entry.
|
65
65
|
# @return [void]
|
66
66
|
def insert_cache_entry(cache_key, entry)
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
@cache_mutex ||= Mutex.new
|
68
|
+
@cache_mutex.synchronize do
|
69
|
+
if @recognition_cache.size >= @recognition_cache_max
|
70
|
+
@recognition_cache.keys.first(@recognition_cache_max / 4).each do |evict_key|
|
71
|
+
@recognition_cache.delete(evict_key)
|
72
|
+
end
|
70
73
|
end
|
74
|
+
@recognition_cache[cache_key] = entry
|
71
75
|
end
|
72
|
-
@recognition_cache[cache_key] = entry
|
73
76
|
end
|
74
77
|
end
|
75
78
|
end
|
@@ -18,10 +18,10 @@ module RubyRoutes
|
|
18
18
|
# @param route [Route] The route to add.
|
19
19
|
# @return [Route] The added route.
|
20
20
|
def add_to_collection(route)
|
21
|
-
@named_routes[route.name] = route if route.named?
|
22
|
-
@radix_tree.add(route.path, route.methods, route)
|
23
21
|
return route if @routes.include?(route) # Prevent duplicate insertion
|
24
22
|
|
23
|
+
@named_routes[route.name] = route if route.named?
|
24
|
+
@radix_tree.add(route.path, route.methods, route)
|
25
25
|
@routes << route
|
26
26
|
route
|
27
27
|
end
|
@@ -74,13 +74,16 @@ module RubyRoutes
|
|
74
74
|
#
|
75
75
|
# @return [void]
|
76
76
|
def clear!
|
77
|
-
@
|
78
|
-
@
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
77
|
+
@cache_mutex ||= Mutex.new
|
78
|
+
@cache_mutex.synchronize do
|
79
|
+
@routes.clear
|
80
|
+
@named_routes.clear
|
81
|
+
@recognition_cache.clear
|
82
|
+
@cache_hits = 0
|
83
|
+
@cache_misses = 0
|
84
|
+
@radix_tree = RadixTree.new
|
85
|
+
RubyRoutes::Utility::KeyBuilderUtility.clear!
|
86
|
+
end
|
84
87
|
end
|
85
88
|
|
86
89
|
# Get the number of routes.
|
@@ -90,18 +90,6 @@ module RubyRoutes
|
|
90
90
|
route.generate_path(params)
|
91
91
|
end
|
92
92
|
|
93
|
-
# Replay recorded calls on the router instance.
|
94
|
-
#
|
95
|
-
# This method replays all the recorded route definitions and other
|
96
|
-
# configuration calls on the given router instance.
|
97
|
-
#
|
98
|
-
# @param router [Router] The router instance to replay calls on.
|
99
|
-
# @return [void]
|
100
|
-
def replay_recorded_calls(router)
|
101
|
-
# Placeholder for actual implementation
|
102
|
-
# Iterate over recorded calls and apply them to the router
|
103
|
-
end
|
104
|
-
|
105
93
|
private
|
106
94
|
|
107
95
|
# Set up the radix tree for structural path matching.
|
@@ -17,6 +17,12 @@ module RubyRoutes
|
|
17
17
|
def build
|
18
18
|
router = Router.new
|
19
19
|
validate_calls(@recorded_calls)
|
20
|
+
RubyRoutes::Constant::RECORDED_METHODS.each do |method_name|
|
21
|
+
define_method(method_name) do |*arguments, &definition_block|
|
22
|
+
@recorded_calls << [__method__, arguments, definition_block]
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
20
26
|
router.finalize!
|
21
27
|
router
|
22
28
|
end
|
@@ -42,7 +42,10 @@ module RubyRoutes
|
|
42
42
|
# @return [Array<Array(Symbol, Array, Proc|NilClass)>]
|
43
43
|
# A snapshot of the recorded calls to avoid external mutation.
|
44
44
|
def recorded_calls
|
45
|
-
|
45
|
+
# Deep-copy each recorded call’s args array and freeze the result to prevent mutation
|
46
|
+
@recorded_calls
|
47
|
+
.map { |(method_name, args, block)| [method_name, args.dup.freeze, block] }
|
48
|
+
.freeze
|
46
49
|
end
|
47
50
|
|
48
51
|
# Initialize the Builder.
|
@@ -72,26 +75,6 @@ module RubyRoutes
|
|
72
75
|
nil
|
73
76
|
end
|
74
77
|
end
|
75
|
-
|
76
|
-
private
|
77
|
-
|
78
|
-
# Validate the recorded calls.
|
79
|
-
#
|
80
|
-
# This method ensures that all recorded calls use valid router methods
|
81
|
-
# as defined in `RubyRoutes::Constant::RECORDED_METHODS`.
|
82
|
-
#
|
83
|
-
# @param recorded_calls [Array<Array(Symbol, Array, Proc|NilClass)>]
|
84
|
-
# The recorded calls to validate.
|
85
|
-
# @raise [ArgumentError] If any recorded call uses an invalid method.
|
86
|
-
# @return [void]
|
87
|
-
def validate_calls(recorded_calls)
|
88
|
-
allowed_router_methods = RubyRoutes::Constant::RECORDED_METHODS
|
89
|
-
recorded_calls.each do |(router_method, _arguments, _definition_block)|
|
90
|
-
unless router_method.is_a?(Symbol) && allowed_router_methods.include?(router_method)
|
91
|
-
raise ArgumentError, "Invalid router method: #{router_method.inspect}"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
78
|
end
|
96
79
|
end
|
97
80
|
end
|
@@ -83,7 +83,12 @@ module RubyRoutes
|
|
83
83
|
via = options[:via]
|
84
84
|
raise ArgumentError, 'match requires :via (e.g., via: [:get, :post])' if via.nil? || Array(via).empty?
|
85
85
|
|
86
|
-
|
86
|
+
normalized_via = Array(via)
|
87
|
+
opts = options.dup
|
88
|
+
# Keep :via in opts only if BuildHelpers expects it; otherwise delete.
|
89
|
+
# If BuildHelpers infers from the second arg, delete it:
|
90
|
+
opts.delete(:via)
|
91
|
+
add_route(path, build_route_options(opts, normalized_via))
|
87
92
|
self
|
88
93
|
end
|
89
94
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'build_helpers'
|
4
|
+
require_relative '../utility/inflector_utility'
|
4
5
|
|
5
6
|
module RubyRoutes
|
6
7
|
class Router
|
@@ -13,8 +14,6 @@ module RubyRoutes
|
|
13
14
|
module ResourceHelpers
|
14
15
|
include RubyRoutes::Router::BuildHelpers
|
15
16
|
|
16
|
-
private
|
17
|
-
|
18
17
|
# Define RESTful routes for a resource.
|
19
18
|
#
|
20
19
|
# @param resource_name [Symbol, String] The name of the resource.
|
@@ -35,6 +34,8 @@ module RubyRoutes
|
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
37
|
+
private
|
38
|
+
|
38
39
|
# Prepare options by removing the `:to` key if present.
|
39
40
|
#
|
40
41
|
# @param options [Hash] The options hash.
|
data/lib/ruby_routes/router.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
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'
|
8
9
|
module RubyRoutes
|
@@ -87,11 +88,16 @@ module RubyRoutes
|
|
87
88
|
return self if @frozen
|
88
89
|
|
89
90
|
@frozen = true
|
91
|
+
if @route_set.respond_to?(:finalize!)
|
92
|
+
@route_set.finalize!
|
93
|
+
else
|
94
|
+
@route_set.freeze
|
95
|
+
end
|
96
|
+
@route_utils.freeze if @route_utils.respond_to?(:freeze)
|
90
97
|
@scope_stack.freeze
|
91
98
|
@concerns.freeze
|
92
99
|
self
|
93
100
|
end
|
94
|
-
|
95
101
|
# Check if the router is frozen.
|
96
102
|
#
|
97
103
|
# @return [Boolean] `true` if the router is frozen, `false` otherwise.
|
@@ -99,12 +105,17 @@ module RubyRoutes
|
|
99
105
|
!!@frozen
|
100
106
|
end
|
101
107
|
|
102
|
-
# Define
|
108
|
+
# Define the root route.
|
103
109
|
#
|
104
110
|
# @param options [Hash] The options for the root route.
|
105
111
|
# @return [Router] self.
|
106
112
|
def root(options = {})
|
107
|
-
|
113
|
+
ensure_unfrozen!
|
114
|
+
if options[:to]
|
115
|
+
add_route('/', via: [:get], **options)
|
116
|
+
else
|
117
|
+
add_route('/', controller: 'root', action: :index, via: [:get], **options)
|
118
|
+
end
|
108
119
|
self
|
109
120
|
end
|
110
121
|
|
@@ -117,6 +128,7 @@ module RubyRoutes
|
|
117
128
|
# @param nested_block [Proc] The block for nested routes.
|
118
129
|
# @return [Router] self.
|
119
130
|
def resources(resource_name, options = {}, &nested_block)
|
131
|
+
ensure_unfrozen!
|
120
132
|
define_resource_routes(resource_name, options, &nested_block)
|
121
133
|
self
|
122
134
|
end
|
@@ -127,6 +139,7 @@ module RubyRoutes
|
|
127
139
|
# @param options [Hash] The options for the resource.
|
128
140
|
# @return [Router] self.
|
129
141
|
def resource(resource_name, options = {})
|
142
|
+
ensure_unfrozen!
|
130
143
|
singular = RubyRoutes::Utility::InflectorUtility.singularize(resource_name.to_s)
|
131
144
|
controller = options[:controller] || singular
|
132
145
|
define_singular_routes(singular, controller, options)
|
@@ -141,6 +154,7 @@ module RubyRoutes
|
|
141
154
|
# @param block [Proc] The block for nested routes.
|
142
155
|
# @return [Router] self.
|
143
156
|
def namespace(namespace_name, options = {}, &block)
|
157
|
+
ensure_unfrozen!
|
144
158
|
push_scope({ path: "/#{namespace_name}", module: namespace_name }.merge(options)) do
|
145
159
|
instance_eval(&block) if block
|
146
160
|
end
|
@@ -152,6 +166,7 @@ module RubyRoutes
|
|
152
166
|
# @param block [Proc] The block for nested routes.
|
153
167
|
# @return [Router] self.
|
154
168
|
def scope(options_or_path = {}, &block)
|
169
|
+
ensure_unfrozen!
|
155
170
|
scope_entry = options_or_path.is_a?(String) ? { path: options_or_path } : options_or_path
|
156
171
|
push_scope(scope_entry) { instance_eval(&block) if block }
|
157
172
|
end
|
@@ -162,6 +177,7 @@ module RubyRoutes
|
|
162
177
|
# @param block [Proc] The block for nested routes.
|
163
178
|
# @return [Router] self.
|
164
179
|
def constraints(constraints_hash = {}, &block)
|
180
|
+
ensure_unfrozen!
|
165
181
|
push_scope(constraints: constraints_hash) { instance_eval(&block) if block }
|
166
182
|
end
|
167
183
|
|
@@ -171,6 +187,7 @@ module RubyRoutes
|
|
171
187
|
# @param block [Proc] The block for nested routes.
|
172
188
|
# @return [Router] self.
|
173
189
|
def defaults(defaults_hash = {}, &block)
|
190
|
+
ensure_unfrozen!
|
174
191
|
push_scope(defaults: defaults_hash) { instance_eval(&block) if block }
|
175
192
|
end
|
176
193
|
|
@@ -192,6 +209,7 @@ module RubyRoutes
|
|
192
209
|
# @param block [Proc] The block for additional routes.
|
193
210
|
# @return [void]
|
194
211
|
def concerns(*concern_names, &block)
|
212
|
+
ensure_unfrozen!
|
195
213
|
concern_names.each do |name|
|
196
214
|
concern_block = @concerns[name]
|
197
215
|
raise "Concern '#{name}' not found" unless concern_block
|
@@ -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
|
-
#
|
42
|
-
# Values: frozen uppercase `String`
|
42
|
+
# Now uses SmallLru for LRU eviction instead of simple clearing.
|
43
43
|
#
|
44
|
-
#
|
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::METHOD_CACHE_MAX_SIZE)
|
50
46
|
|
51
47
|
# Normalize an HTTP method‑like input to a canonical uppercase `String`.
|
52
48
|
#
|
@@ -80,8 +76,9 @@ module RubyRoutes
|
|
80
76
|
def normalize_string_method(method_input)
|
81
77
|
return method_input if already_upper_ascii?(method_input)
|
82
78
|
|
83
|
-
key
|
84
|
-
|
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
82
|
end
|
86
83
|
|
87
84
|
# Normalize a `Symbol` HTTP method.
|
@@ -89,7 +86,10 @@ module RubyRoutes
|
|
89
86
|
# @param method_input [Symbol] The HTTP method input.
|
90
87
|
# @return [String] The normalized HTTP method.
|
91
88
|
def normalize_symbol_method(method_input)
|
92
|
-
SYMBOL_MAP[method_input] ||
|
89
|
+
SYMBOL_MAP[method_input] || begin
|
90
|
+
key = method_input.to_s.freeze
|
91
|
+
METHOD_CACHE.get(key) || METHOD_CACHE.set(key, ascii_upcase(method_input.to_s).freeze)
|
92
|
+
end
|
93
93
|
end
|
94
94
|
|
95
95
|
# Normalize an arbitrary HTTP method input.
|
@@ -100,8 +100,9 @@ module RubyRoutes
|
|
100
100
|
coerced = method_input.to_s
|
101
101
|
return coerced if already_upper_ascii?(coerced)
|
102
102
|
|
103
|
-
key
|
104
|
-
|
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
106
|
end
|
106
107
|
|
107
108
|
# Determine if a `String` consists solely of uppercase ASCII (`A–Z`) or non‑letters.
|
data/lib/ruby_routes/version.rb
CHANGED