1. The purpose of the Crails Router
The Crails router recognizes URLs and dispatches them to a controller's action, or to custom lambdas.
1.1 Connecting URLs to code
When your Crails application receives an incoming request for:
GET /patients/17
it asks the router to match it to a controller action. If the first matching route is:
match_action("GET", "/patients/:id", PatientController, show);
the request is dispatched to the PatientController controller's show method with
{ "id": "17" } in params.
1.2 Configuring the Crails Router
The routes for your application or engine live in the file app/routes.cpp
and typically look like this:
#include "config/router.hpp"
#include "controllers/brands.hpp"
#include "controllers/products.hpp"
ApplicationRouter::ApplicationRouter()
{
match_action("GET", "brands", Brands, index);
scope("brands/:brand_id", [&]()
{
match_action("GET", "", Brands, show);
resource_actions("products", Products);
});
}
1.3 Using the router without controllers
For some simple web application, you might want to strip the controller layer altogether and directly handle queries using functions or lambdas. Here's an example of route matching using lambdas:
void hello_world(Crails::Request& request, std::function<void()> callback)
{
request.response.set_headers("Content-Type", "text/plain");
request.response.set_response(
Crails::HttpStatus::ok,
"Hello world !"
);
callback();
}
ApplicationRouter::ApplicationRouter()
{
match("GET", "hello_world", hello_world);
}
Request object as a shared pointer,
so that it doesn't get deleted. Use request.shared_from_this() to that end.
2. Resource Routing
Resource routing allows you to quickly declare all of the common routes for a given resourceful controller.
A single call to resources can declare all of the necessary routes for your
index, show, new, edit,
create, update, and destroy actions.
2.1 Resources on the web
Browsers request pages from Crails by making a request for a URL using a specific HTTP method,
such as GET, POST, PATCH, PUT, and DELETE.
Each method is a request to perform an operation on the resource. A resource route maps a number of related
requests to actions in a single controller.
When your Rails application receives an incoming request for:
DELETE /photos/17
it asks the router to map it to a controller action. If the first matching route is:
resource_actions("photos", PhotosController);
Crails would dispatch that request to the destroy method on PhotosController with
{ id: "17" } in params.
2.2 CRUD, Verbs and Actions
In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to a specific CRUD operation in a database. A single entry in the routing file, such as:
resource_actions("photos", PhotosController);
creates seven different routes in your application, all mapping to the PhotosController:
| HTTP Verb | Path | Controller::Action | Used for |
|---|---|---|---|
| GET | /photos | PhotosController::index | display a list of all photos |
| GET | /photos/new | PhotosController::new_ | return an HTML form for creating a new photo |
| POST | /photos | PhotosController::create | create a new photo |
| GET | /photos/:id | PhotosController::show | display a specific photo |
| GET | /photos/:id/edit | PhotosController::edit | return an HTML form for editing a photo |
| PUT | /photos/:id | PhotosController::update | update a specific photo |
| DELETE | /photos/:id | PhotosController::destroy | delete a specific photo |
resource_actions("photos", PhotosController) above a
match_action("GET", "photos/poll", PhotosController, poll), the show action's route
for the resource_action line will be matched before the match_action line.
To fix this, move the match_action line above the resource_actions line so that it
is matched first.
2.3 CRUD-only
resource_actions is better fitted for HTML applications. When developing a web API, you
will probably be only interested in CRUD actions. In which case, you should use crud_actions:
crud_actions("photos", PhotosController);
This helper works the same as resource_actions, but strips away the edit and
new_ routes, which you won't need in an API.
2.4 Scopes and Routing
You may wish to organize groups of controllers under a scope. Most commonly, you might group a number of
administrative controllers under an admin scope.
scope("admin", [this]()
{
resource_actions("articles", Admin::ArticlesController);
resource_actions("comments", Admin::CommentsController);
});
This will create a number of routes for each of the articles and comments controller.
For Admin::ArticlesController, Rails will create:
| HTTP Verb | Path | Controller::Action |
|---|---|---|
| GET | /admin/articles | Admin::ArticlesController::index |
| GET | /admin/articles/new | Admin::ArticlesController::new_ |
| POST | /admin/articles | Admin::ArticlesController::create |
| GET | /admin/articles/:id | Admin::ArticlesController::show |
| GET | /admin/articles/:id/edit | Admin::ArticlesController::edit |
| PUT | /admin/articles/:id | Admin::ArticlesController::update |
| DELETE | /admin/articles/:id | Admin::ArticlesController::destroy |
3. Non-Resourceful Routes
3.1 Bound parameters
When you set up a regular route, you supply a series of symbols that Crails maps to parts of an incoming HTTP request. For example, consider this route:
match_action("GET", "/photos(/:id)?", PhotosController, display);
If an incoming request of /photos/1 is processed by this route (because it hasn't matched any
previous route in the file), then the result will be to invoke the display method of the
PhotosController, and to make the final parameter "1" available as
params["id"].
This route will also route the incoming request of /photos to PhotosController::display,
since :id is an optional parameter, denoted by question mark after the parentheses section.
3.2 Dynamic segments
You can set up as many dynamic segments within a regular route as you like. Any segment will be available
to the action as part of params. If you set up this route:
match_action("GET", "/photos/:id/:user_id", PhotosController, show);
An incoming path of /photos/1/2 will be dispatched to the show action of the
PhotosController. params["id"] will be "1", and
params["user_id"] will be "2".
3.3 Static segments
You can specify static segments when creating a route by not prepending a colon to a segment:
match_action("GET", "/photos/:id/with_user/:user_id", PhotosController, show);
This route would respond to paths such as /photos/1/with_user/2.
In this case, params would be { id: "1", user_id: "2" }.
3.4 The Query String
The params will also include any parameters from the query string. For example, with this route:
match_action("GET", "/photos/:id", PhotosController, show);
An incoming path of /photos/1?user_id=2 will be dispatched to the show method of PhotosController.
params will be { id: "1", user_id: "2" }.
3.5 HTTP Verb Constraints
In general, you should use the GET, POST, PUT, PATCH, and DELETE methods to constrain a route to a particular verb. You can also left the verb parameter unspecified to match every verbs at once:
match_action("", "/photos", PhotosController, show);