Module: Ruact::ServerFunctions::RouteSource

Defined in:
lib/ruact/server_functions/route_source.rb

Overview

Story 9.3 — derives v2 server-function entries from the Rails route table.

The route table is the single source of truth (FR61): every non-GET routed action on a controller that includes Ruact::Server is a callable server function. As of Story 9.9 this is the SOLE codegen source consumed by Snapshot — it reads the drawn routes directly.

Pure by construction: RouteSource.collect takes the route set and two resolver callables (host predicate + override lookup). The railtie passes the real constant-resolving implementations; unit specs inject lambdas so the derivation table is testable without booting controllers.

Derivation table (locked, ADR addendum 2026-06-09)

js_identifier = lowerCamel(action) + Namespace*(Pascal) + Resource(Pascal)

  • Resource word — singular for the RESTful writes (create/update/ destroy) and for any member route (path carries :id); plural for a custom collection route. Examples: posts#createcreatePost, posts#publish (member) → publishPost, posts#publish_all (collection) → publishAllPosts, resource :session #createcreateSession.
  • Namespace — PascalCased and inserted between verb and resource (prefix, NOT flat): admin/posts#createcreateAdminPost, admin/reports/posts#createcreateAdminReportsPost. Prefixing keeps the merged JS namespace collision-free by construction (a flat scheme would force admin/posts#create and posts#create to collide).
  • PATCH/PUTresources emits both verbs for update; they collapse to one entry with http_method: "PATCH" (Rails' primary verb).

See Also:

  • "Story 9.3"

Constant Summary collapse

MUTATION_VERBS =

Verbs that expose a callable server function. GET/HEAD are pages.

%w[POST PUT PATCH DELETE].freeze
RESTFUL_WRITES =

The RESTful writes whose JS name uses the SINGULAR resource even though create is technically a collection route.

%w[create update destroy].freeze
VERB_PRIORITY =

When the same controller#action is routed under several verbs (the update PATCH/PUT pair), keep the first by this priority.

{ "PATCH" => 0, "PUT" => 1, "POST" => 2, "DELETE" => 3 }.freeze

Class Method Summary collapse

Class Method Details

.collect(route_set, host_predicate: nil, overrides_for: nil) ⇒ Array<Hash>

Collects v2 mutation entries from route_set.

Parameters:

  • route_set (#routes)

    anything exposing #routes (a ActionDispatch::Routing::RouteSet, or Rails.application.routes).

  • host_predicate (#call) (defaults to: nil)

    controller_path(String) -> Boolean — true when that controller includes Ruact::Server. Defaults to real constant resolution.

  • overrides_for (#call) (defaults to: nil)

    controller_path(String) -> Hash{String=>String} — the ruact_function_name override map (action name → js identifier) for that controller. Defaults to real constant resolution.

Returns:

  • (Array<Hash>)

    entries (string keys) sorted by js_identifier; shape: js_identifier, kind (always "action"), http_method, path, segments (Array), controller, action.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ruact/server_functions/route_source.rb', line 65

def collect(route_set, host_predicate: nil, overrides_for: nil)
  host_predicate ||= method(:default_host?)
  overrides_for  ||= method(:default_overrides_for)

  by_key = {}
  route_set.routes.each do |route|
    verb = route.verb.to_s
    next unless MUTATION_VERBS.include?(verb)

    controller = route.defaults[:controller]
    action = route.defaults[:action]
    next if controller.nil? || action.nil?
    next unless host_predicate.call(controller)

    key = [controller, action]
    existing = by_key[key]
    # PATCH/PUT collapse: keep the higher-priority verb only.
    next if existing && verb_rank(verb) >= verb_rank(existing["http_method"])

    by_key[key] = build_entry(route, verb, controller, action, overrides_for)
  end

  entries = by_key.values.sort_by { |entry| entry["js_identifier"] }
  detect_collisions!(entries)
  entries
end