Class: Ruact::ServerFunctions::StandaloneDispatcher

Inherits:
Object
  • Object
show all
Defined in:
lib/ruact/server_functions/standalone_dispatcher.rb

Overview

Story 8.3 — execution path for STANDALONE server actions (host modules that ‘extend Ruact::ServerAction`). Invoked by EndpointController#dispatch_action when the resolved registry entry’s host is a Module rather than an ‘ActionController::Base` subclass.

Differences from the controller-hosted path (Story 8.1):

- No `host_class.dispatch` — no Rails `process_action` callback
  chain, no `before_action` filters, no `rescue_from` on the host.
  The dispatcher is in charge of the entire response cycle.
- The block runs via `instance_exec` on a fresh
  {Ruact::ServerFunctions::StandaloneContext}, not on a controller
  instance. The context exposes `params` / `session` /
  `current_user` / `request` / `cookies` / `headers`; it does NOT
  expose `render` / `redirect_to` / `head` (the block's return
  value IS the response).

Same contract for response shape (parity with Story 8.1):

- `nil` block return                          → 204 No Content
- Hash / Array / scalar block return          → 200 + JSON body
- `raise Ruact::ActionError.new(status:, body:)` → that status + JSON body

Defined Under Namespace

Classes: Result

Class Method Summary collapse

Class Method Details

.apply_to_response(result, response) ⇒ Object

Writes a Result onto an ‘ActionDispatch::Response`. Used by tests/benches that drive the dispatcher directly (the request-cycle path goes through `EndpointController#dispatch_action` which calls `render` / `head` so Rails’ ‘ImplicitRender` does not interfere).



79
80
81
82
83
84
85
86
87
88
# File 'lib/ruact/server_functions/standalone_dispatcher.rb', line 79

def apply_to_response(result, response)
  response.status = result.status
  if result.body.nil? || result.body.empty?
    response.headers.delete("Content-Type")
    response.body = ""
  else
    response.headers["Content-Type"] = result.content_type if result.content_type
    response.body = result.body
  end
end

.dispatch(entry, request, response = nil) ⇒ Result

Returns render directive describing the response.

Parameters:

  • entry (Ruact::ServerFunctions::RegistryEntry)
  • request (ActionDispatch::Request)
  • response (ActionDispatch::Response, nil) (defaults to: nil)

    when non-nil, the dispatcher writes directives onto the response and marks it committed. Tests typically pass nil and apply the Result manually.

Returns:

  • (Result)

    render directive describing the response.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ruact/server_functions/standalone_dispatcher.rb', line 44

def dispatch(entry, request, response = nil)
  begin
    raw_args = extract_args(request)
  rescue JSON::ParserError => e
    # Story 8.3 review R3 — mirror the controller-DSL path's
    # structured 400 contract (see `Ruact::Controller#ruact_action`,
    # Re-run-4 2026-05-15). A malformed `application/json` body
    # is a client bug; surface it as JSON {error} + 400 so the
    # runtime's RuactActionError surface reports it cleanly.
    result = build_malformed_json_result(entry, e)
    apply_to_response(result, response) if response
    return result
  end

  params = ActionController::Parameters.new(raw_args)
  context = StandaloneContext.new(params: params, request: request)

  result =
    begin
      raw = context.instance_exec(params, &entry.block)
      build_success_result(raw)
    rescue Ruact::ActionError => e
      build_error_result(e)
    end

  maybe_warn_unread_current_user(entry, context)
  apply_to_response(result, response) if response
  result
end