Class: Takagi::CompositeRouter

Inherits:
Object
  • Object
show all
Defined in:
lib/takagi/composite_router.rb,
sig/takagi/composite_router.rbs

Overview

Composite router that routes requests to mounted controllers

The CompositeRouter handles prefix-based routing, matching incoming requests to the appropriate controller based on mount paths.

Examples:

router = CompositeRouter.new
router.mount(TelemetryController, '/telemetry')
router.mount(ConfigController, '/config')

handler, params = router.find_route('POST', '/telemetry/data')

Defined Under Namespace

Classes: MountedController

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCompositeRouter

Returns a new instance of CompositeRouter.



37
38
39
40
# File 'lib/takagi/composite_router.rb', line 37

def initialize
  @mounted = []
  @logger = Takagi.logger
end

Instance Attribute Details

#loggerLogger (readonly)

Returns the value of attribute logger.

Returns:



44
45
46
# File 'sig/takagi/composite_router.rbs', line 44

def logger
  @logger
end

#mountedArray[MountedController] (readonly)

Returns the value of attribute mounted.

Returns:



43
44
45
# File 'sig/takagi/composite_router.rbs', line 43

def mounted
  @mounted
end

Instance Method Details

#all_routesArray[String]

Get all routes from all controllers

Returns:

  • (Array[String])


101
102
103
104
105
106
107
108
109
# File 'lib/takagi/composite_router.rb', line 101

def all_routes
  @mounted.flat_map do |mounted|
    mounted.router.all_routes.map do |route|
      method, path = route.split(' ', 2)
      full_path = mounted.mount_path == '/' ? path : File.join(mounted.mount_path, path)
      "#{method} #{full_path}"
    end
  end
end

#find_controller_for_path(path) ⇒ Class?

Find the controller class that handles a given path

Parameters:

  • path (String)

    Request path

Returns:

  • (Class, nil)

    Controller class or nil if no match



139
140
141
142
# File 'lib/takagi/composite_router.rb', line 139

def find_controller_for_path(path)
  mounted = find_mounted_controller(path)
  mounted&.controller_class
end

#find_mounted_controller(path) ⇒ MountedController?

Find the mounted controller for a path

Parameters:

  • path (String)

Returns:



152
153
154
155
156
157
158
# File 'lib/takagi/composite_router.rb', line 152

def find_mounted_controller(path)
  # Find all controllers that could handle this path
  matches = @mounted.select { |m| m.handles?(path) }

  # Return the one with the longest mount path (most specific)
  matches.max_by { |m| m.mount_path.length }
end

#find_route(method, path) ⇒ [Proc?, Hash[Symbol, String]]

Find a route handler

Parameters:

  • method (String)
  • path (String)

Returns:

  • ([Proc?, Hash[Symbol, String]])


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/takagi/composite_router.rb', line 79

def find_route(method, path)
  @logger.debug "CompositeRouter: Looking for #{method} #{path}"

  # Find matching mounted controller (longest prefix match)
  mounted = find_mounted_controller(path)

  unless mounted
    @logger.debug "CompositeRouter: No mounted controller for #{path}"
    return [nil, {}]
  end

  # Get relative path within controller
  relative = mounted.relative_path(path)
  @logger.debug "CompositeRouter: Routing to #{mounted.controller_class} with path #{relative}"

  # Find route in controller's router
  mounted.router.find_route(method, relative)
end

Get link format entries from all controllers

Returns:



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/takagi/composite_router.rb', line 114

def link_format_entries
  @mounted.flat_map do |mounted|
    mounted.router.link_format_entries.map do |entry|
      # Create a copy with the full path
      entry_copy = entry.dup
      full_path = mounted.mount_path == '/' ? entry.path : File.join(mounted.mount_path, entry.path)

      # Update the path in the copy
      entry_copy.instance_variable_set(:@path, full_path)
      entry_copy
    end
  end
end

#mount(controller_class, at: nil) ⇒ void

This method returns an undefined value.

Mount a controller at a path

Parameters:

  • controller_class (singleton(Controller))
  • at: (String, nil) (defaults to: nil)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/takagi/composite_router.rb', line 51

def mount(controller_class, at: nil)
  path = at || controller_class.mount_path

  raise ArgumentError, "Controller #{controller_class} has no mount path" unless path

  # Normalize path
  path = "/#{path}" unless path.start_with?('/')
  path = path.chomp('/') unless path == '/'

  mounted = MountedController.new(
    controller_class,
    path,
    controller_class.router,
    controller_class.nested_controllers
  )

  @mounted << mounted
  @logger.debug "Mounted #{controller_class} at #{path}"

  # Recursively mount nested controllers
  mount_nested_controllers(mounted)
end

#mount_nested_controllers(parent) ⇒ void

This method returns an undefined value.

Recursively mount nested controllers

Parameters:



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/takagi/composite_router.rb', line 164

def mount_nested_controllers(parent)
  parent.nested_controllers.each do |child_class|
    # Child's mount path is already resolved to include parent path
    child_mount_path = child_class.mount_path

    next unless child_mount_path

    child_mounted = MountedController.new(
      child_class,
      child_mount_path,
      child_class.router,
      child_class.nested_controllers
    )

    @mounted << child_mounted
    @logger.debug "Mounted nested #{child_class} at #{child_mount_path}"

    # Recursively mount children's children
    mount_nested_controllers(child_mounted)
  end
end

#mounted_controllersArray[MountedController]

Get all mounted controllers

Returns:



131
132
133
# File 'lib/takagi/composite_router.rb', line 131

def mounted_controllers
  @mounted
end