ruby_routes 2.2.0 → 2.3.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 +232 -162
- data/lib/ruby_routes/constant.rb +137 -18
- data/lib/ruby_routes/lru_strategies/hit_strategy.rb +31 -4
- data/lib/ruby_routes/lru_strategies/miss_strategy.rb +21 -0
- data/lib/ruby_routes/node.rb +75 -33
- data/lib/ruby_routes/radix_tree/finder.rb +164 -0
- data/lib/ruby_routes/radix_tree/inserter.rb +98 -0
- data/lib/ruby_routes/radix_tree.rb +79 -227
- data/lib/ruby_routes/route/check_helpers.rb +109 -0
- data/lib/ruby_routes/route/constraint_validator.rb +159 -0
- data/lib/ruby_routes/route/param_support.rb +202 -0
- data/lib/ruby_routes/route/path_builder.rb +86 -0
- data/lib/ruby_routes/route/path_generation.rb +102 -0
- data/lib/ruby_routes/route/query_helpers.rb +56 -0
- data/lib/ruby_routes/route/segment_compiler.rb +163 -0
- data/lib/ruby_routes/route/small_lru.rb +93 -18
- data/lib/ruby_routes/route/validation_helpers.rb +151 -0
- data/lib/ruby_routes/route/warning_helpers.rb +54 -0
- data/lib/ruby_routes/route.rb +124 -501
- data/lib/ruby_routes/route_set/cache_helpers.rb +174 -0
- data/lib/ruby_routes/route_set/collection_helpers.rb +127 -0
- data/lib/ruby_routes/route_set.rb +120 -133
- data/lib/ruby_routes/router/build_helpers.rb +100 -0
- data/lib/ruby_routes/router/builder.rb +96 -0
- data/lib/ruby_routes/router/http_helpers.rb +135 -0
- data/lib/ruby_routes/router/resource_helpers.rb +137 -0
- data/lib/ruby_routes/router/scope_helpers.rb +109 -0
- data/lib/ruby_routes/router.rb +193 -181
- data/lib/ruby_routes/segment.rb +28 -8
- data/lib/ruby_routes/segments/base_segment.rb +40 -4
- data/lib/ruby_routes/segments/dynamic_segment.rb +48 -12
- data/lib/ruby_routes/segments/static_segment.rb +43 -7
- data/lib/ruby_routes/segments/wildcard_segment.rb +56 -12
- data/lib/ruby_routes/string_extensions.rb +52 -15
- data/lib/ruby_routes/url_helpers.rb +106 -24
- data/lib/ruby_routes/utility/inflector_utility.rb +35 -0
- data/lib/ruby_routes/utility/key_builder_utility.rb +161 -84
- data/lib/ruby_routes/utility/method_utility.rb +137 -0
- data/lib/ruby_routes/utility/path_utility.rb +75 -28
- data/lib/ruby_routes/utility/route_utility.rb +30 -2
- data/lib/ruby_routes/version.rb +3 -1
- data/lib/ruby_routes.rb +68 -11
- metadata +27 -7
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../constant'
|
4
|
+
|
5
|
+
module RubyRoutes
|
6
|
+
class Router
|
7
|
+
# BuildHelpers
|
8
|
+
#
|
9
|
+
# Small, focused utilities used while constructing routes and routers.
|
10
|
+
module BuildHelpers
|
11
|
+
# Build the router from recorded calls.
|
12
|
+
#
|
13
|
+
# This method creates a new `Router` instance, validates the recorded calls,
|
14
|
+
# finalizes the router (making it immutable), and returns the frozen instance.
|
15
|
+
#
|
16
|
+
# @return [Router] The finalized router.
|
17
|
+
def build
|
18
|
+
router = Router.new
|
19
|
+
validate_calls(@recorded_calls)
|
20
|
+
router.freeze
|
21
|
+
router.finalize!
|
22
|
+
router
|
23
|
+
end
|
24
|
+
|
25
|
+
# Build route options.
|
26
|
+
#
|
27
|
+
# This method creates a copy of the base options and conditionally adds
|
28
|
+
# the `:via` and `:to` keys if they are not already present or need to be overridden.
|
29
|
+
#
|
30
|
+
# @param base_options [Hash] The base options for the route.
|
31
|
+
# @param via_sym [Symbol, nil] The HTTP method (e.g., `:get`, `:post`).
|
32
|
+
# @param to_string [String, nil] The controller#action string (e.g., `"users#index"`).
|
33
|
+
# @return [Hash] The updated route options.
|
34
|
+
def build_route_options(base_options, via_sym = nil, to_string = nil)
|
35
|
+
force_via = !via_sym.nil? && base_options[:via] != via_sym
|
36
|
+
add_to = !to_string.nil? && !base_options.key?(:to)
|
37
|
+
return base_options unless force_via || add_to
|
38
|
+
|
39
|
+
options_copy = base_options.dup
|
40
|
+
options_copy[:via] = via_sym if force_via
|
41
|
+
options_copy[:to] = to_string if add_to
|
42
|
+
options_copy
|
43
|
+
end
|
44
|
+
|
45
|
+
# Helper: Build collection routes inside a resource scope.
|
46
|
+
#
|
47
|
+
# This method defines routes for collection-level actions (e.g., `index`, `new`, `create`)
|
48
|
+
# within the scope of a resource.
|
49
|
+
#
|
50
|
+
# @param opts [Hash] The options to apply to the routes.
|
51
|
+
# @param to_index [String] The controller#action string for the `index` action.
|
52
|
+
# @param to_new [String] The controller#action string for the `new` action.
|
53
|
+
# @param to_create [String] The controller#action string for the `create` action.
|
54
|
+
# @return [void]
|
55
|
+
def build_collection_routes(opts, to_index, to_new, to_create)
|
56
|
+
add_route('', build_route_options(opts, :get, to_index))
|
57
|
+
add_route('/new', build_route_options(opts, :get, to_new))
|
58
|
+
add_route('', build_route_options(opts, :post, to_create))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Helper: Build member routes inside a resource scope.
|
62
|
+
#
|
63
|
+
# This method defines routes for member-level actions (e.g., `show`, `edit`, `update`, `destroy`)
|
64
|
+
# within the scope of a resource.
|
65
|
+
#
|
66
|
+
# @param opts [Hash] The options to apply to the routes.
|
67
|
+
# @param to_show [String] The controller#action string for the `show` action.
|
68
|
+
# @param to_edit [String] The controller#action string for the `edit` action.
|
69
|
+
# @param to_update [String] The controller#action string for the `update` action.
|
70
|
+
# @param to_destroy [String] The controller#action string for the `destroy` action.
|
71
|
+
# @return [void]
|
72
|
+
def build_member_routes(opts, to_show, to_edit, to_update, to_destroy)
|
73
|
+
add_route('/:id', build_route_options(opts, :get, to_show))
|
74
|
+
add_route('/:id/edit', build_route_options(opts, :get, to_edit))
|
75
|
+
add_route('/:id', opts.merge(via: %i[put patch], to: to_update))
|
76
|
+
add_route('/:id', build_route_options(opts, :delete, to_destroy))
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Validate the recorded calls.
|
82
|
+
#
|
83
|
+
# This method ensures that all recorded calls use valid router methods
|
84
|
+
# as defined in `RubyRoutes::Constant::RECORDED_METHODS`.
|
85
|
+
#
|
86
|
+
# @param recorded_calls [Array<Array(Symbol, Array, Proc|NilClass)>]
|
87
|
+
# The recorded calls to validate.
|
88
|
+
# @raise [ArgumentError] If any recorded call uses an invalid method.
|
89
|
+
# @return [void]
|
90
|
+
def validate_calls(recorded_calls)
|
91
|
+
allowed_router_methods = RubyRoutes::Constant::RECORDED_METHODS
|
92
|
+
recorded_calls.each do |(router_method, _arguments, _definition_block)|
|
93
|
+
unless router_method.is_a?(Symbol) && allowed_router_methods.include?(router_method)
|
94
|
+
raise ArgumentError, "Invalid router method: #{router_method.inspect}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../constant'
|
4
|
+
require_relative 'build_helpers'
|
5
|
+
|
6
|
+
module RubyRoutes
|
7
|
+
class Router
|
8
|
+
# Builder
|
9
|
+
#
|
10
|
+
# Records routing DSL invocations without mutating a live Router.
|
11
|
+
# Later, `#build` replays the recorded calls on a fresh Router,
|
12
|
+
# finalizes it (immutability), and returns the frozen instance.
|
13
|
+
#
|
14
|
+
# Benefits:
|
15
|
+
# - Decouples route declaration time from Router instantiation.
|
16
|
+
# - Enables reuse (same blueprint -> multiple routers).
|
17
|
+
# - Safe to construct on boot and share across threads after build.
|
18
|
+
#
|
19
|
+
# Usage:
|
20
|
+
# builder = RubyRoutes::Router::Builder.new do
|
21
|
+
# namespace :api do
|
22
|
+
# resources :users
|
23
|
+
# end
|
24
|
+
# get '/health', to: 'system#health'
|
25
|
+
# end
|
26
|
+
# router = builder.build # finalized (router.frozen? == true)
|
27
|
+
#
|
28
|
+
# Supported DSL methods are mirrored here. Blocks are stored as Procs;
|
29
|
+
# serialization of blocks is not supported.
|
30
|
+
#
|
31
|
+
# @api internal
|
32
|
+
class Builder
|
33
|
+
include RubyRoutes::Router::BuildHelpers
|
34
|
+
|
35
|
+
# Array of recorded calls: [method_symbol, args_array, block].
|
36
|
+
#
|
37
|
+
# Each tuple contains:
|
38
|
+
# - The method name (as a Symbol).
|
39
|
+
# - The arguments (as an Array).
|
40
|
+
# - The block (as a Proc or `nil`).
|
41
|
+
#
|
42
|
+
# @return [Array<Array(Symbol, Array, Proc|NilClass)>]
|
43
|
+
# A snapshot of the recorded calls to avoid external mutation.
|
44
|
+
def recorded_calls
|
45
|
+
@recorded_calls.dup.freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
# Initialize the Builder.
|
49
|
+
#
|
50
|
+
# This method initializes the `@recorded_calls` array and optionally
|
51
|
+
# evaluates the provided block in the context of the Builder instance.
|
52
|
+
#
|
53
|
+
# @yield [definition_block] Runs the routing DSL in a recording context (optional).
|
54
|
+
# @return [void]
|
55
|
+
def initialize(&definition_block)
|
56
|
+
@recorded_calls = []
|
57
|
+
instance_eval(&definition_block) if definition_block
|
58
|
+
end
|
59
|
+
|
60
|
+
# ---- DSL Recording -------------------------------------------------
|
61
|
+
# Dynamically define methods for all DSL methods specified in
|
62
|
+
# `RubyRoutes::Constant::RECORDED_METHODS`. Each method records its
|
63
|
+
# invocation (method name, arguments, and block) in `@recorded_calls`.
|
64
|
+
#
|
65
|
+
# @param *arguments [Array] The arguments passed to the DSL method.
|
66
|
+
# @param definition_block [Proc, nil] The block passed to the DSL method.
|
67
|
+
# @return [nil]
|
68
|
+
RubyRoutes::Constant::RECORDED_METHODS.each do |method_name|
|
69
|
+
define_method(method_name) do |*arguments, &definition_block|
|
70
|
+
@recorded_calls << [__method__, arguments, definition_block]
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Validate the recorded calls.
|
78
|
+
#
|
79
|
+
# This method ensures that all recorded calls use valid router methods
|
80
|
+
# as defined in `RubyRoutes::Constant::RECORDED_METHODS`.
|
81
|
+
#
|
82
|
+
# @param recorded_calls [Array<Array(Symbol, Array, Proc|NilClass)>]
|
83
|
+
# The recorded calls to validate.
|
84
|
+
# @raise [ArgumentError] If any recorded call uses an invalid method.
|
85
|
+
# @return [void]
|
86
|
+
def validate_calls(recorded_calls)
|
87
|
+
allowed_router_methods = RubyRoutes::Constant::RECORDED_METHODS
|
88
|
+
recorded_calls.each do |(router_method, _arguments, _definition_block)|
|
89
|
+
unless router_method.is_a?(Symbol) && allowed_router_methods.include?(router_method)
|
90
|
+
raise ArgumentError, "Invalid router method: #{router_method.inspect}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'build_helpers'
|
4
|
+
require_relative 'scope_helpers'
|
5
|
+
require_relative 'resource_helpers'
|
6
|
+
|
7
|
+
module RubyRoutes
|
8
|
+
class Router
|
9
|
+
# HttpHelpers
|
10
|
+
#
|
11
|
+
# DSL methods exposing HTTP verb helpers (`get`, `post`, `put`, `patch`, `delete`, `match`)
|
12
|
+
# and small wiring helpers. Resource- and nested-related logic is delegated
|
13
|
+
# to `Router::ResourceHelpers` to keep this module focused.
|
14
|
+
#
|
15
|
+
# This module provides methods to define routes for various HTTP verbs, apply
|
16
|
+
# scopes, and manage route definitions. It also includes helper methods for
|
17
|
+
# defining singular resource routes.
|
18
|
+
module HttpHelpers
|
19
|
+
include RubyRoutes::Router::BuildHelpers
|
20
|
+
include RubyRoutes::Router::ScopeHelpers
|
21
|
+
include RubyRoutes::Router::ResourceHelpers
|
22
|
+
|
23
|
+
# ---- HTTP Verb Helpers -------------------------------------------------
|
24
|
+
|
25
|
+
# Define a GET route.
|
26
|
+
#
|
27
|
+
# @param path [String] The path for the route.
|
28
|
+
# @param options [Hash] The options for the route.
|
29
|
+
# @return [Router] Returns self for chaining.
|
30
|
+
def get(path, options = {})
|
31
|
+
add_route(path, build_route_options(options, :get))
|
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
|
73
|
+
end
|
74
|
+
|
75
|
+
# Define a route for multiple HTTP methods.
|
76
|
+
#
|
77
|
+
# @param path [String] The path for the route.
|
78
|
+
# @param options [Hash] The options for the route.
|
79
|
+
# - `:via` [Array<Symbol>] The HTTP methods to allow (e.g., `[:get, :post]`).
|
80
|
+
# @raise [ArgumentError] If `:via` is not provided or is empty.
|
81
|
+
# @return [Router] Returns self for chaining.
|
82
|
+
def match(path, options = {})
|
83
|
+
via = options[:via]
|
84
|
+
raise ArgumentError, 'match requires :via (e.g., via: [:get, :post])' if via.nil? || Array(via).empty?
|
85
|
+
|
86
|
+
add_route(path, options)
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Add a route to the router.
|
93
|
+
#
|
94
|
+
# This method applies the current scope to the route and defines it using
|
95
|
+
# the route utilities.
|
96
|
+
#
|
97
|
+
# @param path [String] The path for the route.
|
98
|
+
# @param options [Hash] The options for the route.
|
99
|
+
# @return [void]
|
100
|
+
def add_route(path, options = {})
|
101
|
+
ensure_unfrozen!
|
102
|
+
scoped = apply_scope(path, options)
|
103
|
+
@route_utils.define(scoped[:path], scoped)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Ensure the router is not frozen.
|
107
|
+
#
|
108
|
+
# @raise [RuntimeError] If the router is frozen.
|
109
|
+
# @return [void]
|
110
|
+
def ensure_unfrozen!
|
111
|
+
raise 'Router finalized (immutable)' if @frozen
|
112
|
+
end
|
113
|
+
|
114
|
+
# Define routes for a singular resource.
|
115
|
+
#
|
116
|
+
# This method defines routes for a singular resource (e.g., `/profile`),
|
117
|
+
# including standard RESTful actions like `show`, `new`, `create`, `edit`,
|
118
|
+
# `update`, and `destroy`.
|
119
|
+
#
|
120
|
+
# @param singular [String] The name of the singular resource.
|
121
|
+
# @param controller [String] The name of the controller handling the resource.
|
122
|
+
# @param options [Hash] Additional options for the routes.
|
123
|
+
# @return [void]
|
124
|
+
def define_singular_routes(singular, controller, options)
|
125
|
+
get "/#{singular}", options.merge(to: "#{controller}#show")
|
126
|
+
get "/#{singular}/new", options.merge(to: "#{controller}#new")
|
127
|
+
post "/#{singular}", options.merge(to: "#{controller}#create")
|
128
|
+
get "/#{singular}/edit", options.merge(to: "#{controller}#edit")
|
129
|
+
put "/#{singular}", options.merge(to: "#{controller}#update")
|
130
|
+
patch "/#{singular}", options.merge(to: "#{controller}#update")
|
131
|
+
delete "/#{singular}", options.merge(to: "#{controller}#destroy")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'build_helpers'
|
4
|
+
|
5
|
+
module RubyRoutes
|
6
|
+
class Router
|
7
|
+
# ResourceHelpers: resource / nested-resource related helpers extracted
|
8
|
+
# from HttpHelpers to reduce method length and ABC complexity.
|
9
|
+
#
|
10
|
+
# This module provides methods for defining RESTful routes for resources,
|
11
|
+
# handling nested resources, and generating metadata for resource paths
|
12
|
+
# and controller actions.
|
13
|
+
module ResourceHelpers
|
14
|
+
include RubyRoutes::Router::BuildHelpers
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Define RESTful routes for a resource.
|
19
|
+
#
|
20
|
+
# @param resource_name [Symbol, String] The name of the resource.
|
21
|
+
# @param options [Hash] Options for customizing the resource routes.
|
22
|
+
# - `:path` [String] Custom path for the resource.
|
23
|
+
# - `:controller` [String] Custom controller name.
|
24
|
+
# - `:nested` [Symbol, String] Name of the nested resource.
|
25
|
+
# @param nested_block [Proc] A block for defining nested routes.
|
26
|
+
# @return [void]
|
27
|
+
def define_resource_routes(resource_name, options = {}, &nested_block)
|
28
|
+
meta = resource_meta(resource_name, options)
|
29
|
+
opts = prepare_options(options)
|
30
|
+
|
31
|
+
push_scope(path: "/#{meta[:resource_path]}") do
|
32
|
+
build_routes(opts, meta)
|
33
|
+
handle_nested_option(options, opts)
|
34
|
+
apply_nested_block(nested_block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Prepare options by removing the `:to` key if present.
|
39
|
+
#
|
40
|
+
# @param options [Hash] The options hash.
|
41
|
+
# @return [Hash] The prepared options.
|
42
|
+
def prepare_options(options)
|
43
|
+
options.key?(:to) ? options.dup.tap { |h| h.delete(:to) } : options
|
44
|
+
end
|
45
|
+
|
46
|
+
# Build collection and member routes for a resource.
|
47
|
+
#
|
48
|
+
# @param opts [Hash] The options hash.
|
49
|
+
# @param meta [Hash] The resource metadata.
|
50
|
+
# @return [void]
|
51
|
+
def build_routes(opts, meta)
|
52
|
+
build_collection_routes(opts, meta[:to_index], meta[:to_new], meta[:to_create])
|
53
|
+
build_member_routes(opts, meta[:to_show], meta[:to_edit], meta[:to_update], meta[:to_destroy])
|
54
|
+
end
|
55
|
+
|
56
|
+
# Apply a nested block of routes within the scope of a resource.
|
57
|
+
#
|
58
|
+
# @param nested_block [Proc] The block defining nested routes.
|
59
|
+
# @return [void]
|
60
|
+
def apply_nested_block(nested_block)
|
61
|
+
return unless nested_block
|
62
|
+
|
63
|
+
push_scope(path: '/:id') { instance_eval(&nested_block) }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Prepare resource metadata (path/controller/action strings).
|
67
|
+
#
|
68
|
+
# @param resource_name [Symbol, String] The name of the resource.
|
69
|
+
# @param options [Hash] Options for customizing the resource metadata.
|
70
|
+
# @return [Hash] The resource metadata.
|
71
|
+
def resource_meta(resource_name, options)
|
72
|
+
base_name = resource_name.to_s
|
73
|
+
resource_path = options[:path] ? options[:path].to_s : RubyRoutes::Utility::InflectorUtility.pluralize(base_name)
|
74
|
+
controller = options[:controller] || RubyRoutes::Utility::InflectorUtility.pluralize(base_name)
|
75
|
+
build_meta_hash(resource_path, controller)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Build a metadata hash for a resource.
|
79
|
+
#
|
80
|
+
# @param resource_path [String] The resource path.
|
81
|
+
# @param controller [String] The controller name.
|
82
|
+
# @return [Hash] The metadata hash.
|
83
|
+
def build_meta_hash(resource_path, controller)
|
84
|
+
actions = %w[index new create show edit update destroy]
|
85
|
+
meta_hash = actions.each_with_object({}) do |action, hash|
|
86
|
+
hash[:"to_#{action}"] = "#{controller}##{action}"
|
87
|
+
end
|
88
|
+
|
89
|
+
meta_hash.merge(
|
90
|
+
resource_path: resource_path,
|
91
|
+
controller: controller
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Handle the `nested:` shorthand option for resources.
|
96
|
+
#
|
97
|
+
# @param options [Hash] The options hash.
|
98
|
+
# @param opts [Hash] The prepared options hash.
|
99
|
+
# @return [void]
|
100
|
+
def handle_nested_option(options, opts)
|
101
|
+
return unless options[:nested]
|
102
|
+
|
103
|
+
nested_name = options[:nested].to_s
|
104
|
+
nested_path = RubyRoutes::Utility::InflectorUtility.pluralize(nested_name)
|
105
|
+
build_nested_routes(nested_path, opts)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Build nested resource routes.
|
109
|
+
#
|
110
|
+
# @param nested_path [String] The path for the nested resource.
|
111
|
+
# @param opts [Hash] The options hash.
|
112
|
+
# @return [void]
|
113
|
+
def build_nested_routes(nested_path, opts)
|
114
|
+
push_scope(path: '/:id') do
|
115
|
+
push_scope(path: "/#{nested_path}") do
|
116
|
+
add_nested_routes(nested_path, opts)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Add routes for a nested resource.
|
122
|
+
#
|
123
|
+
# @param nested_path [String] The path for the nested resource.
|
124
|
+
# @param opts [Hash] The options hash.
|
125
|
+
# @return [void]
|
126
|
+
def add_nested_routes(nested_path, opts)
|
127
|
+
add_route('', build_route_options(opts, :get, "#{nested_path}#index"))
|
128
|
+
add_route('/new', build_route_options(opts, :get, "#{nested_path}#new"))
|
129
|
+
add_route('', build_route_options(opts, :post, "#{nested_path}#create"))
|
130
|
+
add_route('/:nested_id', build_route_options(opts, :get, "#{nested_path}#show"))
|
131
|
+
add_route('/:nested_id/edit', build_route_options(opts, :get, "#{nested_path}#edit"))
|
132
|
+
add_route('/:nested_id', opts.merge(via: %i[put patch], to: "#{nested_path}#update"))
|
133
|
+
add_route('/:nested_id', build_route_options(opts, :delete, "#{nested_path}#destroy"))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyRoutes
|
4
|
+
class Router
|
5
|
+
# ScopeHelpers: encapsulate scope application logic
|
6
|
+
#
|
7
|
+
# This module provides methods for managing and applying scopes in the routing DSL.
|
8
|
+
# Scopes allow you to define nested paths, modules, defaults, and constraints
|
9
|
+
# that apply to a group of routes.
|
10
|
+
module ScopeHelpers
|
11
|
+
private
|
12
|
+
|
13
|
+
# Push a scope onto the scope stack.
|
14
|
+
#
|
15
|
+
# This method temporarily adds a scope entry to the scope stack, executes the
|
16
|
+
# given block, and ensures the scope is removed afterward.
|
17
|
+
#
|
18
|
+
# @param scope_entry [Hash] The scope entry to push onto the stack.
|
19
|
+
# @yield The block to execute within the scope.
|
20
|
+
# @return [void]
|
21
|
+
def push_scope(scope_entry)
|
22
|
+
ensure_unfrozen!
|
23
|
+
return unless block_given?
|
24
|
+
|
25
|
+
@scope_stack.push(scope_entry)
|
26
|
+
|
27
|
+
begin
|
28
|
+
yield
|
29
|
+
ensure
|
30
|
+
@scope_stack.pop
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Apply all scopes to the given path and options.
|
35
|
+
#
|
36
|
+
# This method iterates through the scope stack in reverse order, applying
|
37
|
+
# path, module, defaults, and constraints from each scope to the given path
|
38
|
+
# and options.
|
39
|
+
#
|
40
|
+
# @param path [String] The base path to apply scopes to.
|
41
|
+
# @param options [Hash] The options to apply scopes to.
|
42
|
+
# @return [Hash] The scoped options, including the updated path.
|
43
|
+
def apply_scope(path, options)
|
44
|
+
scoped_options = options.dup
|
45
|
+
scoped_path = path.to_s.dup
|
46
|
+
|
47
|
+
@scope_stack.reverse_each do |scope|
|
48
|
+
apply_path_scope(scope, scoped_path)
|
49
|
+
apply_module_scope(scope, scoped_options)
|
50
|
+
apply_defaults_scope(scope, scoped_options)
|
51
|
+
apply_constraints_scope(scope, scoped_options)
|
52
|
+
end
|
53
|
+
|
54
|
+
scoped_options[:path] = scoped_path
|
55
|
+
scoped_options
|
56
|
+
end
|
57
|
+
|
58
|
+
# Apply the path from a scope to the given path.
|
59
|
+
#
|
60
|
+
# @param scope [Hash] The scope containing the path.
|
61
|
+
# @param scoped_path [String] The path to prepend the scope's path to.
|
62
|
+
# @return [void]
|
63
|
+
def apply_path_scope(scope, scoped_path)
|
64
|
+
scoped_path.prepend(scope[:path]) if scope[:path]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Apply the module from a scope to the given options.
|
68
|
+
#
|
69
|
+
# This method updates the `:to` or `:controller` option to include the module
|
70
|
+
# from the scope.
|
71
|
+
#
|
72
|
+
# @param scope [Hash] The scope containing the module.
|
73
|
+
# @param scoped_options [Hash] The options to update with the module.
|
74
|
+
# @return [void]
|
75
|
+
def apply_module_scope(scope, scoped_options)
|
76
|
+
return unless scope[:module]
|
77
|
+
|
78
|
+
if scoped_options[:to]
|
79
|
+
controller, action = scoped_options[:to].to_s.split('#', 2)
|
80
|
+
scoped_options[:to] = "#{scope[:module]}/#{controller}##{action}"
|
81
|
+
elsif scoped_options[:controller]
|
82
|
+
scoped_options[:controller].prepend("#{scope[:module]}/")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Apply the defaults from a scope to the given options.
|
87
|
+
#
|
88
|
+
# @param scope [Hash] The scope containing the defaults.
|
89
|
+
# @param scoped_options [Hash] The options to update with the defaults.
|
90
|
+
# @return [void]
|
91
|
+
def apply_defaults_scope(scope, scoped_options)
|
92
|
+
return unless scope[:defaults]
|
93
|
+
|
94
|
+
scoped_options[:defaults] = (scoped_options[:defaults] || {}).merge(scope[:defaults])
|
95
|
+
end
|
96
|
+
|
97
|
+
# Apply the constraints from a scope to the given options.
|
98
|
+
#
|
99
|
+
# @param scope [Hash] The scope containing the constraints.
|
100
|
+
# @param scoped_options [Hash] The options to update with the constraints.
|
101
|
+
# @return [void]
|
102
|
+
def apply_constraints_scope(scope, scoped_options)
|
103
|
+
return unless scope[:constraints]
|
104
|
+
|
105
|
+
scoped_options[:constraints] = (scoped_options[:constraints] || {}).merge(scope[:constraints])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|