ticket

Ticket is under construction. These docs follow the code as it lands, so expect changes. See the roadmap

Routing

A routes zone maps requests to controller actions, Rails style. It generates the router, a route table and a typed helper for every named route.

Setting up

Put the zone in the module your polar.toml points main at, usually src/routes.px. It needs Std.Http, Std.Option, Std.Id and Ticket.Conn in uses, plus every controller it names.

module Routes

uses
  Std.Http
  Std.Id
  Std.Option
  Ticket.Conn
  Admin.StatsController
  Controllers.ArticlesController
  Controllers.PagesController

hosts
  Node

routes
  root         -> PagesController.home
  get  /about  -> PagesController.about  as about

  resources articles  -> ArticlesController  only index show new create

  namespace admin
    get  /stats  -> StatsController.show  as stats

exports
  Node

A controller is named by its local name: the last segment of its module path, or its as alias.

Verb lines

The verbs are get, post, put, patch and delete. A :name segment is a path param. Add as name to get a name_path helper. A verb line without as gets no helper.

routes
  root                       -> PagesController.home
  get   /about               -> PagesController.about    as about
  post  /webhooks/:source    -> PagesController.webhook  as webhook

Resources

resources posts -> PostsController expands to the seven Rails actions, in this order:

MethodPathActionHelper
GET/postsindexposts_path()
GET/posts/newnewnew_post_path()
POST/postscreate
GET/posts/:idshowpost_path(id)
GET/posts/:id/editeditedit_post_path(id)
PATCH, PUT/posts/:idupdate
DELETE/posts/:iddestroy

Use only or except (not both) to pick actions. Matching is first-match in this order, which is why /posts/new isn't taken by /posts/:id.

Nesting, member and collection

Indented lines belong to the entry above. Nested resources go under the singular of the parent, member routes under /posts/:id/…, and collection routes under /posts/….

routes
  resources posts              -> PostsController
    resources comments         -> CommentsController  only create destroy
    member
      post  publish            -> PostsController.publish
    collection
      get   drafts             -> PostsController.drafts

  namespace admin
    resources users            -> UsersController  except destroy

That gives you post_comments_path(post_id), post_comment_path(post_id, id), publish_post_path(id), drafts_posts_path() and admin_users_path(). A namespace prefixes paths with /admin and helper names with admin_.

When a table name has no simple singular, name it with as: resources people -> PeopleController as person.

Path helpers

A param named id or ending in _id takes an Id. Any other param takes a String, which is URL-encoded.

post_path(Id(3))                    // "/posts/3"
post_comment_path(Id(2), Id(9))     // "/posts/2/comments/9"
webhook_path("a b")                 // "/webhooks/a%20b"

Controllers and views get helpers from Paths. The routes module imports every controller, so a controller can't import it back. The zone also generates a sibling module Paths with the same helpers and no controller imports. Write uses Paths and call Paths.article_path(id).

How requests match

  • Trailing and doubled slashes don't matter: /posts/3/ matches /posts/:id.
  • A POST with a form field _method of patch, put or delete is routed as that method.
  • HEAD runs the GET action and sends no body.
  • No path matches: the launcher answers 404. A path matches but the method doesn't: 405, with an allow header.
  • Path params are strings. /posts/abc still reaches show, and the action decides what to do with it.

Mistakes are compile errors

routes
  resources posts  -> PostController
error[POLAR0901]: no module `PostController` in `uses`
  --> src/unknown_controller.px:12:23
   |
12 |   resources posts  -> PostController
   |                       ^^^^^^^^^^^^^^
   |
   = help: a controller is a module in `uses`, named by its last segment or its `as` alias

Duplicate routes, duplicate helpers, unknown verbs and unknown actions in only are reported the same way. polar fmt lines up the -> column in each block.