Class: Ruact::ServerFunctions::EndpointController
- Inherits:
-
ActionController::Base
- Object
- ActionController::Base
- Ruact::ServerFunctions::EndpointController
- Includes:
- ErrorRendering
- Defined in:
- lib/ruact/server_functions/endpoint_controller.rb
Overview
Story 8.1 — the single gem-mounted Rails controller backing ‘POST /__ruact/fn/:name`. It resolves the URL `:name` parameter to a registered RegistryEntry, allocates a fresh instance of the entry’s host controller class, and delegates dispatch to that instance via Rails’ standard ‘dispatch(action_name, request, response)` plumbing.
This indirection is what gives ‘ruact_action` blocks access to the host controller’s ‘current_user`, `session`, `before_action` chain, Pundit / ActionPolicy authorization, and `rescue_from` handlers — the block runs inside an honest controller instance, not in some gem-internal context.
The ‘dispatch_action` action below is the ONLY public action on this controller — there is no `:create`, `:update`, etc.; the host’s actions are reached indirectly via the wrapper method ‘_ruact_action<symbol>` that Controller#ruact_action defines.
Class Method Summary collapse
-
.standalone_host?(host) ⇒ Boolean
Story 8.3 — positive check for the standalone host shape.
Instance Method Summary collapse
-
#dispatch_action ⇒ Object
‘POST /__ruact/fn/:name` (mounted by `Ruact::Railtie`).
Class Method Details
.standalone_host?(host) ⇒ Boolean
Story 8.3 — positive check for the standalone host shape. A host is standalone iff it’s a Module (and not a Class) that extends ‘Ruact::ServerAction`. The class hierarchy `Class < Module` means `is_a?(Module)` also matches Classes; we exclude Classes explicitly.
158 159 160 161 162 163 164 |
# File 'lib/ruact/server_functions/endpoint_controller.rb', line 158 def self.standalone_host?(host) return false if host.nil? return false if host.is_a?(Class) return false unless host.is_a?(Module) host.singleton_class.include?(Ruact::ServerAction) end |
Instance Method Details
#dispatch_action ⇒ Object
‘POST /__ruact/fn/:name` (mounted by `Ruact::Railtie`).
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/ruact/server_functions/endpoint_controller.rb', line 94 def dispatch_action entry = @__ruact_entry return render_unknown(@__ruact_name_sym) unless entry host = entry.controller if Ruact::ServerFunctions::EndpointController.standalone_host?(host) # Call StandaloneDispatcher WITHOUT passing the response so Rails' # `ImplicitRender` does not see an uncommitted response (writing # directly to `response.body =` would otherwise be silently # overwritten by the implicit-render 204). Apply the dispatcher's # Result directive via render/head, which Rails recognises as # rendered output. result = Ruact::ServerFunctions::StandaloneDispatcher.dispatch(entry, request) return apply_standalone_result(result) end unless host.is_a?(Class) return render( json: { error: "ruact action :#{@__ruact_name_sym} has an invalid host shape — " \ "expected a Controller class or a Module that extends Ruact::ServerAction" }, status: :internal_server_error ) end host_class = host # Re-run-2 (2026-05-14) — rebuild `request.path_parameters` so that # the host action sees `controller`/`action` keys describing ITSELF, # not the gem-endpoint route. Without this, `params[:controller]` # inside the host's action body returns # `"ruact/server_functions/endpoint"` and `params[:action]` returns # `"dispatch_action"` — which breaks `controller_name` / # `controller_path` / Pundit policy resolution / any code that reads # the routing identity. Restore after dispatch so the endpoint # response can be rendered with its own identity intact. # Re-run-4 (2026-05-15) — DROP `name: raw_name` from the swap. # The host action does not need the routing function name (it's # already inferable from `action_name`), and keeping it in # `path_parameters` made `params[:name]` inside the host action / # before_action chain return the route function name instead of # a legitimate submitted body field named `:name`. Only # `controller`/`action` are swapped — those are required for # `controller_name` / `controller_path` / Pundit / instrumentation. original_path_parameters = request.path_parameters.dup host_path_parameters = { controller: host_class.controller_path, action: @__ruact_name_sym.to_s } request.path_parameters = host_path_parameters # Thread-local sentinel allows the public action method to be # invoked only here, not from a wildcard route the host may have # set up — see the guard inside the defined method. Thread.current[:__ruact_dispatching] = @__ruact_name_sym host_class.dispatch(@__ruact_name_sym.to_s, request, response) ensure Thread.current[:__ruact_dispatching] = nil request.path_parameters = original_path_parameters if original_path_parameters end |