ruby_routes 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/ruby_routes/cache_setup.rb +32 -0
- data/lib/ruby_routes/constant.rb +18 -3
- data/lib/ruby_routes/lru_strategies/hit_strategy.rb +1 -6
- data/lib/ruby_routes/node.rb +14 -87
- data/lib/ruby_routes/radix_tree/finder.rb +88 -46
- 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 +54 -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 +2 -2
- data/lib/ruby_routes/route/constraint_validator.rb +24 -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 +51 -5
- data/lib/ruby_routes/route/validation_helpers.rb +6 -46
- data/lib/ruby_routes/route.rb +32 -59
- data/lib/ruby_routes/route_set/cache_helpers.rb +24 -25
- data/lib/ruby_routes/route_set/collection_helpers.rb +7 -16
- data/lib/ruby_routes/route_set.rb +36 -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/dynamic_segment.rb +1 -1
- 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 +19 -7
- data/lib/ruby_routes/version.rb +1 -1
- data/lib/ruby_routes.rb +3 -1
- metadata +12 -2
- data/lib/ruby_routes/string_extensions.rb +0 -65
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../route/small_lru'
|
4
|
+
|
3
5
|
module RubyRoutes
|
4
6
|
class RouteSet
|
5
7
|
# CacheHelpers: extracted cache, request-key, and eviction logic to reduce
|
@@ -8,6 +10,8 @@ module RubyRoutes
|
|
8
10
|
# This module provides methods for managing caches, request keys, and
|
9
11
|
# implementing eviction policies for route recognition.
|
10
12
|
module CacheHelpers
|
13
|
+
|
14
|
+
attr_reader :small_lru
|
11
15
|
# Recognition cache statistics.
|
12
16
|
#
|
13
17
|
# @return [Hash] A hash containing:
|
@@ -16,42 +20,27 @@ module RubyRoutes
|
|
16
20
|
# - `:hit_rate` [Float] The cache hit rate as a percentage.
|
17
21
|
# - `:size` [Integer] The current size of the recognition cache.
|
18
22
|
def cache_stats
|
19
|
-
total_requests = @
|
23
|
+
total_requests = @small_lru.hits + @small_lru.misses
|
20
24
|
{
|
21
|
-
hits: @
|
22
|
-
misses: @
|
23
|
-
hit_rate: total_requests.zero? ? 0.0 : (@
|
25
|
+
hits: @small_lru.hits,
|
26
|
+
misses: @small_lru.misses,
|
27
|
+
hit_rate: total_requests.zero? ? 0.0 : (@small_lru.hits.to_f / total_requests * 100.0),
|
24
28
|
size: @recognition_cache.size
|
25
29
|
}
|
26
30
|
end
|
27
31
|
|
28
32
|
private
|
29
33
|
|
30
|
-
# Set up caches and request-key ring.
|
31
|
-
#
|
32
|
-
# Initializes the internal data structures for managing routes, named routes,
|
33
|
-
# recognition cache, and request-key ring buffer.
|
34
|
-
#
|
35
|
-
# @return [void]
|
36
|
-
def setup_caches
|
37
|
-
@routes = []
|
38
|
-
@named_routes = {}
|
39
|
-
@recognition_cache = {}
|
40
|
-
@recognition_cache_max = 2048
|
41
|
-
@cache_hits = 0
|
42
|
-
@cache_misses = 0
|
43
|
-
end
|
44
|
-
|
45
34
|
# Fetch cached recognition entry while updating hit counter.
|
46
35
|
#
|
47
36
|
# @param lookup_key [String] The cache lookup key.
|
48
37
|
# @return [Hash, nil] The cached recognition entry, or `nil` if not found.
|
49
38
|
def fetch_cached_recognition(lookup_key)
|
50
39
|
if (cached_result = @recognition_cache[lookup_key])
|
51
|
-
@
|
40
|
+
@small_lru.increment_hits
|
52
41
|
return cached_result
|
53
42
|
end
|
54
|
-
@
|
43
|
+
@small_lru.increment_misses
|
55
44
|
nil
|
56
45
|
end
|
57
46
|
|
@@ -64,12 +53,22 @@ module RubyRoutes
|
|
64
53
|
# @param entry [Hash] The cache entry.
|
65
54
|
# @return [void]
|
66
55
|
def insert_cache_entry(cache_key, entry)
|
67
|
-
@cache_mutex ||= Mutex.new
|
68
56
|
@cache_mutex.synchronize do
|
69
57
|
if @recognition_cache.size >= @recognition_cache_max
|
70
|
-
|
71
|
-
|
72
|
-
|
58
|
+
# Calculate how many to keep (3/4 of max, rounded down)
|
59
|
+
keep_count = @recognition_cache_max / 4
|
60
|
+
|
61
|
+
# Get the keys to keep (newest 75%, assuming insertion order)
|
62
|
+
keys_to_keep = @recognition_cache.keys.last(keep_count)
|
63
|
+
|
64
|
+
# Get the entries to keep
|
65
|
+
entries_to_keep = @recognition_cache.slice(*keys_to_keep)
|
66
|
+
|
67
|
+
# Clear the entire cache (evicts the oldest 25%)
|
68
|
+
@recognition_cache.clear
|
69
|
+
|
70
|
+
# Re-add the kept entries (3/4)
|
71
|
+
@recognition_cache.merge!(entries_to_keep)
|
73
72
|
end
|
74
73
|
@recognition_cache[cache_key] = entry
|
75
74
|
end
|
@@ -12,7 +12,7 @@ module RubyRoutes
|
|
12
12
|
# Add a route object to internal structures.
|
13
13
|
#
|
14
14
|
# This method adds a route to the internal route collection, updates the
|
15
|
-
#
|
15
|
+
# matching strategy for fast path/method lookups, and registers the route in the
|
16
16
|
# named routes collection if it has a name.
|
17
17
|
#
|
18
18
|
# @param route [Route] The route to add.
|
@@ -21,7 +21,7 @@ module RubyRoutes
|
|
21
21
|
return route if @routes.include?(route) # Prevent duplicate insertion
|
22
22
|
|
23
23
|
@named_routes[route.name] = route if route.named?
|
24
|
-
@
|
24
|
+
@strategy.add(route)
|
25
25
|
@routes << route
|
26
26
|
route
|
27
27
|
end
|
@@ -40,14 +40,14 @@ module RubyRoutes
|
|
40
40
|
|
41
41
|
# Find any route (no params) for a method/path.
|
42
42
|
#
|
43
|
-
# This method searches the
|
43
|
+
# This method searches the matching strategy for a route matching the given HTTP
|
44
44
|
# method and path.
|
45
45
|
#
|
46
46
|
# @param http_method [String, Symbol] The HTTP method (e.g., `:get`, `:post`).
|
47
47
|
# @param path [String] The path to match.
|
48
48
|
# @return [Route, nil] The matching route, or `nil` if no match is found.
|
49
49
|
def find_route(http_method, path)
|
50
|
-
route, _params = @
|
50
|
+
route, _params = @strategy.find(path, http_method)
|
51
51
|
route
|
52
52
|
end
|
53
53
|
|
@@ -69,21 +69,12 @@ module RubyRoutes
|
|
69
69
|
# Clear all routes and caches.
|
70
70
|
#
|
71
71
|
# This method clears the internal route collection, named routes, recognition
|
72
|
-
# cache, and
|
72
|
+
# cache, and matching strategy. It also resets cache hit/miss counters and clears
|
73
73
|
# the global request key cache.
|
74
74
|
#
|
75
75
|
# @return [void]
|
76
|
-
def
|
77
|
-
|
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
|
76
|
+
def clear_routes_and_caches!
|
77
|
+
setup_caches
|
87
78
|
end
|
88
79
|
|
89
80
|
# Get the number of routes.
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'cache_setup'
|
4
|
+
require_relative 'strategies'
|
4
5
|
require_relative 'utility/key_builder_utility'
|
5
6
|
require_relative 'utility/method_utility'
|
6
7
|
require_relative 'route_set/cache_helpers'
|
7
8
|
require_relative 'route_set/collection_helpers'
|
8
9
|
require_relative 'route/param_support'
|
10
|
+
require_relative 'route/path_generation'
|
9
11
|
|
10
12
|
module RubyRoutes
|
11
13
|
# RouteSet
|
@@ -17,7 +19,7 @@ module RubyRoutes
|
|
17
19
|
# - Index named routes.
|
18
20
|
# - Provide fast recognition (method + path → route, params) with
|
19
21
|
# a small in‑memory recognition cache.
|
20
|
-
# - Delegate structural path matching to
|
22
|
+
# - Delegate structural path matching to a configurable strategy.
|
21
23
|
#
|
22
24
|
# Thread Safety:
|
23
25
|
# - RouteSet instances are not fully thread-safe for modifications.
|
@@ -33,14 +35,17 @@ module RubyRoutes
|
|
33
35
|
include RubyRoutes::Utility::MethodUtility
|
34
36
|
include RubyRoutes::RouteSet::CacheHelpers
|
35
37
|
include RubyRoutes::Route::ParamSupport
|
38
|
+
include RubyRoutes::Route::PathGeneration
|
36
39
|
include RubyRoutes::RouteSet::CollectionHelpers
|
40
|
+
include RubyRoutes::CacheSetup
|
37
41
|
|
38
42
|
# Initialize empty collection and caches.
|
39
43
|
#
|
44
|
+
# @param strategy [Class] The matching strategy to use.
|
40
45
|
# @return [void]
|
41
|
-
def initialize
|
46
|
+
def initialize(strategy: Strategies::HybridStrategy)
|
42
47
|
setup_caches
|
43
|
-
|
48
|
+
setup_strategy(strategy)
|
44
49
|
end
|
45
50
|
|
46
51
|
# Recognize a request (method + path) returning route + params.
|
@@ -49,7 +54,7 @@ module RubyRoutes
|
|
49
54
|
# @param path [String] The request path.
|
50
55
|
# @return [Hash, nil] A hash containing the matched route and parameters, or `nil` if no match is found.
|
51
56
|
def match(http_method, path)
|
52
|
-
normalized_method =
|
57
|
+
normalized_method = normalize_http_method(http_method)
|
53
58
|
raw_path = path.to_s
|
54
59
|
lookup_key = cache_key_for_request(normalized_method, raw_path)
|
55
60
|
|
@@ -77,8 +82,7 @@ module RubyRoutes
|
|
77
82
|
# @param params [Hash] The parameters for path generation.
|
78
83
|
# @return [String] The generated path.
|
79
84
|
def generate_path(name, params = {})
|
80
|
-
|
81
|
-
route.generate_path(params)
|
85
|
+
generate_path_from_route(find_named_route(name), params)
|
82
86
|
end
|
83
87
|
|
84
88
|
# Generate path from a direct route reference.
|
@@ -90,25 +94,19 @@ module RubyRoutes
|
|
90
94
|
route.generate_path(params)
|
91
95
|
end
|
92
96
|
|
97
|
+
def clear_counters!
|
98
|
+
clear_routes_and_caches!
|
99
|
+
end
|
100
|
+
|
93
101
|
private
|
94
102
|
|
95
|
-
# Set up the
|
103
|
+
# Set up the matching strategy.
|
96
104
|
#
|
105
|
+
# @param strategy [Class] The matching strategy class.
|
97
106
|
# @return [void]
|
98
|
-
def
|
99
|
-
@
|
100
|
-
|
101
|
-
|
102
|
-
# Normalize the HTTP method for matching.
|
103
|
-
#
|
104
|
-
# @param http_method [String, Symbol] The HTTP method.
|
105
|
-
# @return [String] The normalized HTTP method.
|
106
|
-
def normalize_method_for_match(http_method)
|
107
|
-
if http_method.is_a?(String) && normalize_http_method(http_method).equal?(http_method)
|
108
|
-
http_method
|
109
|
-
else
|
110
|
-
normalize_http_method(http_method)
|
111
|
-
end
|
107
|
+
def setup_strategy(strategy)
|
108
|
+
@strategy_class = strategy
|
109
|
+
@strategy = @strategy_class.new
|
112
110
|
end
|
113
111
|
|
114
112
|
# Perform the route matching process.
|
@@ -117,31 +115,28 @@ module RubyRoutes
|
|
117
115
|
# @param raw_path [String] The raw request path.
|
118
116
|
# @return [Hash, nil] A hash containing the matched route and parameters, or `nil` if no match is found.
|
119
117
|
def perform_match(normalized_method, raw_path)
|
120
|
-
|
121
|
-
matched_route, extracted_params = @radix_tree.find(path_without_query, normalized_method)
|
118
|
+
matched_route, path_params = @strategy.find(raw_path, normalized_method)
|
122
119
|
return nil unless matched_route
|
123
120
|
|
124
|
-
|
125
|
-
|
126
|
-
extracted_params = {}
|
127
|
-
elsif extracted_params.frozen?
|
128
|
-
extracted_params = extracted_params.dup
|
129
|
-
end
|
130
|
-
|
131
|
-
merge_query_params(matched_route, raw_path, extracted_params)
|
132
|
-
merge_defaults(matched_route, extracted_params)
|
133
|
-
build_match_result(matched_route, extracted_params)
|
121
|
+
final_params = build_final_params(matched_route, path_params, raw_path)
|
122
|
+
build_match_result(matched_route, final_params)
|
134
123
|
end
|
135
124
|
|
136
|
-
#
|
125
|
+
# Build the final parameters hash by merging path, query, and default params.
|
137
126
|
#
|
138
127
|
# @param matched_route [Route] The matched route.
|
139
|
-
# @param
|
140
|
-
# @
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
128
|
+
# @param path_params [Hash] The parameters extracted from the path.
|
129
|
+
# @param raw_path [String] The full request path including query string.
|
130
|
+
# @return [Hash] The final, merged parameters hash.
|
131
|
+
def build_final_params(matched_route, path_params, raw_path)
|
132
|
+
# Optimized merge order: defaults -> path -> query
|
133
|
+
# Start with defaults, which have the lowest precedence.
|
134
|
+
final_params = matched_route.defaults.dup
|
135
|
+
# Merge path parameters, which override defaults.
|
136
|
+
final_params.merge!(path_params) if path_params
|
137
|
+
matched_route.merge_query_params_into_hash(final_params, raw_path, nil)
|
138
|
+
|
139
|
+
final_params
|
145
140
|
end
|
146
141
|
|
147
142
|
# Build the match result hash.
|
@@ -157,23 +152,5 @@ module RubyRoutes
|
|
157
152
|
action: matched_route.action
|
158
153
|
}
|
159
154
|
end
|
160
|
-
|
161
|
-
# Obtain a pooled hash for temporary parameters.
|
162
|
-
#
|
163
|
-
# @return [Hash] A thread-local hash for temporary parameter storage.
|
164
|
-
def thread_local_params
|
165
|
-
thread_params = Thread.current[:ruby_routes_params_pool] ||= []
|
166
|
-
thread_params.empty? ? {} : thread_params.pop.clear
|
167
|
-
end
|
168
|
-
|
169
|
-
# Return a parameters hash to the thread-local pool.
|
170
|
-
#
|
171
|
-
# @param params [Hash] The parameters hash to return.
|
172
|
-
# @return [void]
|
173
|
-
def return_params_to_pool(params)
|
174
|
-
params.clear
|
175
|
-
thread_pool = Thread.current[:ruby_routes_params_pool] ||= []
|
176
|
-
thread_pool << params if thread_pool.size < 10 # Limit pool size
|
177
|
-
end
|
178
155
|
end
|
179
156
|
end
|
@@ -17,12 +17,6 @@ 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
|
26
20
|
router.finalize!
|
27
21
|
router
|
28
22
|
end
|
@@ -95,7 +89,7 @@ module RubyRoutes
|
|
95
89
|
def validate_calls(recorded_calls)
|
96
90
|
allowed_router_methods = RubyRoutes::Constant::RECORDED_METHODS
|
97
91
|
recorded_calls.each do |(router_method, _arguments, _definition_block)|
|
98
|
-
unless
|
92
|
+
unless allowed_router_methods.include?(router_method)
|
99
93
|
raise ArgumentError, "Invalid router method: #{router_method.inspect}"
|
100
94
|
end
|
101
95
|
end
|
@@ -32,6 +32,18 @@ module RubyRoutes
|
|
32
32
|
class Builder
|
33
33
|
include RubyRoutes::Router::BuildHelpers
|
34
34
|
|
35
|
+
# Initialize the Builder.
|
36
|
+
#
|
37
|
+
# This method initializes the `@recorded_calls` array and optionally
|
38
|
+
# evaluates the provided block in the context of the Builder instance.
|
39
|
+
#
|
40
|
+
# @yield [definition_block] Runs the routing DSL in a recording context (optional).
|
41
|
+
# @return [void]
|
42
|
+
def initialize(&definition_block)
|
43
|
+
@recorded_calls = []
|
44
|
+
instance_eval(&definition_block) if definition_block
|
45
|
+
end
|
46
|
+
|
35
47
|
# Array of recorded calls: [method_symbol, args_array, block].
|
36
48
|
#
|
37
49
|
# Each tuple contains:
|
@@ -48,18 +60,6 @@ module RubyRoutes
|
|
48
60
|
.freeze
|
49
61
|
end
|
50
62
|
|
51
|
-
# Initialize the Builder.
|
52
|
-
#
|
53
|
-
# This method initializes the `@recorded_calls` array and optionally
|
54
|
-
# evaluates the provided block in the context of the Builder instance.
|
55
|
-
#
|
56
|
-
# @yield [definition_block] Runs the routing DSL in a recording context (optional).
|
57
|
-
# @return [void]
|
58
|
-
def initialize(&definition_block)
|
59
|
-
@recorded_calls = []
|
60
|
-
instance_eval(&definition_block) if definition_block
|
61
|
-
end
|
62
|
-
|
63
63
|
# ---- DSL Recording -------------------------------------------------
|
64
64
|
# Dynamically define methods for all DSL methods specified in
|
65
65
|
# `RubyRoutes::Constant::RECORDED_METHODS`. Each method records its
|
@@ -22,54 +22,13 @@ module RubyRoutes
|
|
22
22
|
|
23
23
|
# ---- HTTP Verb Helpers -------------------------------------------------
|
24
24
|
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
self
|
33
|
-
end
|
34
|
-
|
35
|
-
# Define a POST route.
|
36
|
-
#
|
37
|
-
# @param path [String] The path for the route.
|
38
|
-
# @param options [Hash] The options for the route.
|
39
|
-
# @return [Router] Returns self for chaining.
|
40
|
-
def post(path, options = {})
|
41
|
-
add_route(path, build_route_options(options, :post))
|
42
|
-
self
|
43
|
-
end
|
44
|
-
|
45
|
-
# Define a PUT route.
|
46
|
-
#
|
47
|
-
# @param path [String] The path for the route.
|
48
|
-
# @param options [Hash] The options for the route.
|
49
|
-
# @return [Router] Returns self for chaining.
|
50
|
-
def put(path, options = {})
|
51
|
-
add_route(path, build_route_options(options, :put))
|
52
|
-
self
|
53
|
-
end
|
54
|
-
|
55
|
-
# Define a PATCH route.
|
56
|
-
#
|
57
|
-
# @param path [String] The path for the route.
|
58
|
-
# @param options [Hash] The options for the route.
|
59
|
-
# @return [Router] Returns self for chaining.
|
60
|
-
def patch(path, options = {})
|
61
|
-
add_route(path, build_route_options(options, :patch))
|
62
|
-
self
|
63
|
-
end
|
64
|
-
|
65
|
-
# Define a DELETE route.
|
66
|
-
#
|
67
|
-
# @param path [String] The path for the route.
|
68
|
-
# @param options [Hash] The options for the route.
|
69
|
-
# @return [Router] Returns self for chaining.
|
70
|
-
def delete(path, options = {})
|
71
|
-
add_route(path, build_route_options(options, :delete))
|
72
|
-
self
|
25
|
+
# Metaprogram `get`, `post`, `put`, `patch`, `delete` for DRYness.
|
26
|
+
# These methods define a route for a specific HTTP verb.
|
27
|
+
%i[get post put patch delete].each do |verb|
|
28
|
+
define_method(verb) do |path, options = {}|
|
29
|
+
add_route(path, build_route_options(options, verb))
|
30
|
+
self
|
31
|
+
end
|
73
32
|
end
|
74
33
|
|
75
34
|
# Define a route for multiple HTTP methods.
|
@@ -25,33 +25,43 @@ module RubyRoutes
|
|
25
25
|
# @return [void]
|
26
26
|
def define_resource_routes(resource_name, options = {}, &nested_block)
|
27
27
|
meta = resource_meta(resource_name, options)
|
28
|
-
|
28
|
+
resource_opts = prepare_resource_options(options)
|
29
29
|
|
30
30
|
push_scope(path: "/#{meta[:resource_path]}") do
|
31
|
-
|
32
|
-
handle_nested_option(options,
|
31
|
+
define_resource_actions(resource_opts, meta[:controller])
|
32
|
+
handle_nested_option(options, resource_opts)
|
33
33
|
apply_nested_block(nested_block)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
37
|
private
|
38
38
|
|
39
|
-
# Prepare options
|
39
|
+
# Prepare options for resource routes, removing the `:to` key to avoid conflicts.
|
40
|
+
# This avoids creating a new hash if it's not necessary.
|
40
41
|
#
|
41
42
|
# @param options [Hash] The options hash.
|
42
43
|
# @return [Hash] The prepared options.
|
43
|
-
def
|
44
|
-
options.key?(:to) ? options.
|
44
|
+
def prepare_resource_options(options)
|
45
|
+
options.key?(:to) ? options.except(:to) : options
|
45
46
|
end
|
46
47
|
|
47
|
-
#
|
48
|
+
# Defines the seven standard RESTful actions for a resource.
|
49
|
+
# This method is data-driven to reduce duplication and improve clarity.
|
48
50
|
#
|
49
|
-
# @param
|
50
|
-
# @param
|
51
|
+
# @param resource_opts [Hash] The options for the resource routes.
|
52
|
+
# @param controller [String] The controller name for the actions.
|
53
|
+
# @param member_param [String] The parameter name for member routes (e.g., ':id').
|
51
54
|
# @return [void]
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
+
def define_resource_actions(resource_opts, controller, member_param: ':id')
|
56
|
+
# Collection routes
|
57
|
+
add_route('', build_route_options(resource_opts, :get, "#{controller}#index"))
|
58
|
+
add_route('/new', build_route_options(resource_opts, :get, "#{controller}#new"))
|
59
|
+
add_route('', build_route_options(resource_opts, :post, "#{controller}#create"))
|
60
|
+
# Member routes
|
61
|
+
add_route("/#{member_param}", build_route_options(resource_opts, :get, "#{controller}#show"))
|
62
|
+
add_route("/#{member_param}/edit", build_route_options(resource_opts, :get, "#{controller}#edit"))
|
63
|
+
add_route("/#{member_param}", resource_opts.merge(via: %i[put patch], to: "#{controller}#update"))
|
64
|
+
add_route("/#{member_param}", build_route_options(resource_opts, :delete, "#{controller}#destroy"))
|
55
65
|
end
|
56
66
|
|
57
67
|
# Apply a nested block of routes within the scope of a resource.
|
@@ -103,36 +113,12 @@ module RubyRoutes
|
|
103
113
|
|
104
114
|
nested_name = options[:nested].to_s
|
105
115
|
nested_path = RubyRoutes::Utility::InflectorUtility.pluralize(nested_name)
|
106
|
-
build_nested_routes(nested_path, opts)
|
107
|
-
end
|
108
|
-
|
109
|
-
# Build nested resource routes.
|
110
|
-
#
|
111
|
-
# @param nested_path [String] The path for the nested resource.
|
112
|
-
# @param opts [Hash] The options hash.
|
113
|
-
# @return [void]
|
114
|
-
def build_nested_routes(nested_path, opts)
|
115
116
|
push_scope(path: '/:id') do
|
116
117
|
push_scope(path: "/#{nested_path}") do
|
117
|
-
|
118
|
+
define_resource_actions(opts, nested_path, member_param: ':nested_id')
|
118
119
|
end
|
119
120
|
end
|
120
121
|
end
|
121
|
-
|
122
|
-
# Add routes for a nested resource.
|
123
|
-
#
|
124
|
-
# @param nested_path [String] The path for the nested resource.
|
125
|
-
# @param opts [Hash] The options hash.
|
126
|
-
# @return [void]
|
127
|
-
def add_nested_routes(nested_path, opts)
|
128
|
-
add_route('', build_route_options(opts, :get, "#{nested_path}#index"))
|
129
|
-
add_route('/new', build_route_options(opts, :get, "#{nested_path}#new"))
|
130
|
-
add_route('', build_route_options(opts, :post, "#{nested_path}#create"))
|
131
|
-
add_route('/:nested_id', build_route_options(opts, :get, "#{nested_path}#show"))
|
132
|
-
add_route('/:nested_id/edit', build_route_options(opts, :get, "#{nested_path}#edit"))
|
133
|
-
add_route('/:nested_id', opts.merge(via: %i[put patch], to: "#{nested_path}#update"))
|
134
|
-
add_route('/:nested_id', build_route_options(opts, :delete, "#{nested_path}#destroy"))
|
135
|
-
end
|
136
122
|
end
|
137
123
|
end
|
138
124
|
end
|
@@ -61,21 +61,11 @@ module RubyRoutes
|
|
61
61
|
# @param scoped_path [String] The path to prepend the scope's path to.
|
62
62
|
# @return [void]
|
63
63
|
def apply_path_scope(scope, scoped_path)
|
64
|
-
|
65
|
-
return if
|
66
|
-
|
67
|
-
if path.end_with?('/')
|
68
|
-
if scoped_path.start_with?('/')
|
69
|
-
scoped_path.prepend(path.chomp('/'))
|
70
|
-
else
|
71
|
-
scoped_path.prepend(path)
|
72
|
-
end
|
73
|
-
else
|
74
|
-
scoped_path.prepend(scoped_path.start_with?('/') ? path : "#{path}/")
|
75
|
-
end
|
64
|
+
scope_path = scope[:path]&.to_s
|
65
|
+
return if scope_path.nil? || scope_path.empty?
|
76
66
|
|
77
|
-
|
78
|
-
scoped_path.
|
67
|
+
parts = [scope_path, scoped_path].map { |p| p.to_s.gsub(%r{^/|/$}, '') }.reject(&:empty?)
|
68
|
+
scoped_path.replace("/#{parts.join('/')}")
|
79
69
|
end
|
80
70
|
|
81
71
|
# Apply the module from a scope to the given options.
|
@@ -122,6 +112,28 @@ module RubyRoutes
|
|
122
112
|
|
123
113
|
scoped_options[:constraints] = scope[:constraints].merge(scoped_options[:constraints] || {})
|
124
114
|
end
|
115
|
+
|
116
|
+
# Get the current merged scope from the scope stack
|
117
|
+
#
|
118
|
+
# @return [Hash] The merged scope with combined namespaces
|
119
|
+
def current_scope
|
120
|
+
merged = {}
|
121
|
+
namespace_parts = []
|
122
|
+
|
123
|
+
@scope_stack.each do |scope|
|
124
|
+
if scope[:namespace]
|
125
|
+
namespace_parts << scope[:namespace].to_s
|
126
|
+
end
|
127
|
+
merged.merge!(scope)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Combine namespaces for nested namespace support
|
131
|
+
if namespace_parts.any?
|
132
|
+
merged[:namespace] = namespace_parts.join('/')
|
133
|
+
end
|
134
|
+
|
135
|
+
merged
|
136
|
+
end
|
125
137
|
end
|
126
138
|
end
|
127
139
|
end
|