The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Mojolicious::Plugin::Restify - Route shortcuts & helpers for REST collections

SYNOPSIS

  # Mojolicious example (Mojolicious::Lite isn't supported)
  package MyApp;
  use Mojo::Base 'Mojolicious';

  sub startup {
    my $self = shift;

    # imports the `collection' route shortcut and `restify' helpers
    $self->plugin('Restify');

    # add REST collection endpoints manually
    my $r = $self->routes;
    my $accounts = $r->collection('accounts');      # /accounts
    $accounts->collection('invoices');              # /accounts/:accounts_id/invoices

    # or add the equivalent REST routes with an ARRAYREF (the helper will
    # create chained routes from the path 'accounts/invoices' so you don't need
    # to set ['accounts', 'accounts/invoices'])
    my $r = $self->routes;
    $self->restify->routes($r, ['accounts/invoices']);

    # or add the equivalent REST routes with a HASHREF (might be easier to
    # visualise how collections are chained together)
    my $r = $self->routes;
    $self->restify->routes($r, {
      accounts => {
        invoices => undef
      }
    });
  }

Next create your controller for accounts.

  # Restify controller depicting the REST actions for the /accounts collection.
  # (The name of the controller is the Mojo::Util::camelized version of the
  # collection path.)
  package MyApp::Controller::Accounts;
  use Mojo::Base 'Mojolicious::Controller';

  sub resource_lookup {
    my $c = shift;

    # To consistenly get the element's ID relative to the resource_lookup
    # action, use the helper as shown below. If you need to access an element ID
    # from a collection further up the chain, you can access it from the stash.
    #
    # The naming convention is the name of the collection appended with '_id'.
    # E.g., $c->stash('accounts_id').
    my $account = your_lookup_account_resource_func($c->restify->current_id);

    # By stashing the $account here, it will now be available in the delete,
    # read, patch, and update actions. This resource_lookup action is optional,
    # but added to every collection by default to help reduce your code.
    $c->stash(account => $account);

    # must return a positive value to continue the dispatch chain
    return 1 if $account;

    # inform the end user that this specific resource does not exist
    $c->reply->not_found and return 0;
  }

  sub create { ... }

  sub delete { ... }

  sub list { ... }

  sub read {
    my $c = shift;

    # account was placed in the stash in the resource_lookup action
    $c->render(json => $c->stash('account'));
  }

  sub patch { ... }

  sub update { ... }

  1;

DESCRIPTION

Mojolicious::Plugin::Restify is a Mojolicious::Plugin. It simplifies generating all of the Mojolicious::Routes for a typical REST collection endpoint (e.g., /accounts or /invoices) and maps the common HTTP verbs (DELETE, GET, PATCH, POST, PUT) to underlying controller class methods.

For example, creating a collection called /accounts would create the routes as shown below. N.B. The over option in the example below corresponds to the name of a route condition. See "conditions" in Mojolicious::Routes.

  # The collection route shortcut below creates the following routes, and maps
  # them to controllers of the camelized route's name.
  #
  # Pattern           Methods   Name                        Class::Method Name
  # -------           -------   ----                        ------------------
  # /accounts         *         accounts
  #   +/              GET       "accounts_list"             Accounts::list
  #   +/              POST      "accounts_create"           Accounts::create
  #   +/:accounts_id  *         "accounts"
  #     +/            *         "accounts_resource_lookup"  Accounts::resource_lookup
  #       +/          DELETE    "accounts_delete"           Accounts::delete
  #       +/          GET       "accounts_read"             Accounts::read
  #       +/          PATCH     "accounts_patch"            Accounts::patch
  #       +/          PUT       "accounts_update"           Accounts::update

  # expects the element id (:accounts_id) for this collection to be a uuid
  my $route = $r->collection('accounts', over => 'uuid');

Mojolicious::Plugin::Restify tries not to make too many assumptions, but the author's recent experience writing a REST-based API using Mojolicious has helped shaped this plugin, and might unwittingly express some of his bias.

HELPERS

Mojolicious::Plugin::Restify implements the following helpers.

restify->current_id

  my $id = $c->restify->current_id;

Returns the element id at the current point in the dispatch chain.

This is the only way to guarantee the correct element's resource ID in a Mojolicious::Plugin::Restify action. The resource_lookup action, which is added by default in both "collection" and "restify-routes", is added at different positions of the dispatch chain. As such, the router might not have added the value of any placeholders to the "stash" in Mojolicious::Controller yet.

restify->routes

This helper is a wrapper around the "collection" route shortcut. It facilitates creating REST collections using either an ARRAYREF or HASHREF.

It takes a Mojolicious::Routes object, the paths to create, and optionally options which are passed to the "collection" route shortcut.

  # /accounts
  # /accounts/1234
  $self->restify->routes($self->routes, ['accounts'], {over => 'int'});

  # /invoices
  # /invoices/76be1f53-8363-4ac6-bd83-8b49e07b519c
  $self->restify->routes($self->routes, ['invoices'], {over => 'uuid'});

Maybe you want to chain them.

  # /accounts
  # /accounts/1234
  #   /accounts/1234/invoices
  #   /accounts/1234/invoices/76be1f53-8363-4ac6-bd83-8b49e07b519c
  $self->restify->routes(
    $self->routes,
    ['accounts', ['accounts/invoices' => {over => 'uuid'}]],
    {over => 'int'}
  );
ARRAYREF

Using the elements of the array, invokes "collection", passing any route- specific options.

It will automatically create and chain parent routes if you pass a full path e.g., ['a/very/long/path']. This is equivalent to the shell command mkdir -p.

  my $restify_routes = [
    # /area-codes
    #   /area-codes/:area_codes_id/numbers
    'area-codes/numbers',
    # /news
    'news',
    # /payments
    ['payments' => {over => 'int'}],   # overrides default uuid route condition
    # /users
    #   /users/:users_id/messages
    #     /users/:users_id/messages/:messages_id/recipients
    'users/messages/recipients',
  ];

  $self->restify->routes($self->routes, $restify_routes, {over => 'uuid'});

In its most basic form, REST routes are created from a SCALAR.

  # /accounts
  my $restify_routes = ['accounts'];
HASHREF

Using the key/values of the hash, invokes "collection", passing any route- specific options.

It automatically chains routes to each parent, and progressively builds a namespace as it traverses through each key.

N.B., This was implemented before the ARRAYREF version, and is arguably a bit more confusing. It might be dropped in a later version to simplify the API.

  my $restify_routes = {
    # /area-codes
    #   /area-codes/:area_codes_id/numbers
    'area-codes' => {
      'numbers' => undef
    },
    # /news
    'news' => undef,
    # /payments
    'payments' => [undef, {over => 'int'}],   # overrides default uuid route condition
    # /users
    #   /users/:users_id/messages
    #     /users/:users_id/messages/:messages_id/recipients
    'users' => {
      'messages' => {
        'recipients' => undef
      }
    },
  };

  $self->restify->routes($self->routes, $restify_routes, {over => 'uuid'});

METHODS

Mojolicious::Plugin::Restify inherits all methods from Mojolicious::Plugin and implements the following new ones.

register

  $plugin->register(Mojolicious->new);

Register plugin in Mojolicious application.

ROUTE CONDITIONS

Mojolicious::Plugin::Restify implements the following route conditions. These conditions can be used with the over option in the "collection" shortcut.

Checks are made for the existence of the int, standard and uuid conditions before adding them. This allows you to replace them with your own conditions of the same name by creating them before registering this plugin.

See "Adding-conditions" in Mojolicious::Guides::Routing to add your own.

int

  # /numbers/1        # GOOD
  # /numbers/0        # GOOD
  # /numbers/one      # BAD
  # /numbers/-1       # BAD
  # /numbers/0.114    # BAD (the standard :placeholder notation doesn't allow a '.')

  my $r = $self->routes;
  $r->collection('numbers', over => 'int');

A Mojolicious route condition (see "conditions" in Mojolicious::Routes) which restricts a route's collection's element id to whole positive integers which are >= 0.

standard

  my $r = $self->routes;
  $r->collection('numbers', over => 'standard');

A collection's element resource ID is captured using "Standard-placeholders" in Mojolicious::Guides::Routing. This route condition allows everything the standard placeholder allows, which is similar to the regular expression ([^/.]+).

This is the default over option for a "collection".

uuid

  # /uuids/8ebef0d0-d6cf-11e4-8830-0800200c9a66     GOOD
  # /uuids/8EBEF0D0-D6CF-11E4-8830-0800200C9A66     GOOD
  # /uuids/8ebef0d0d6cf11e488300800200c9a66         GOOD
  # /uuids/malformed-uuid                           BAD

  my $r = $self->routes;
  $r->collection('uuids', over => 'uuid');

A Mojolicious route condition (see "conditions" in Mojolicious::Routes) which restricts a route's collection's element id to UUIDs only (with or without the separating hyphens).

ROUTE SHORTCUTS

Mojolicious::Plugin::Restify implements the following route shortcuts.

collection

  my $r = $self->routes;
  $r->collection('accounts');
  $r->collection('accounts', collection_method_map => {delete => 'delete_collection'});
  $r->collection('accounts', controller            => 'differentmodule');
  $r->collection('accounts', element               => 0);
  $r->collection('accounts', element_method_map    => {get => 'read'});
  $r->collection('accounts', over                  => 'uuid');
  $r->collection('accounts', placeholder           => '*');
  $r->collection('accounts', prefix                => 'v1');
  $r->collection('accounts', resource_lookup       => '0');

A Mojolicious route shortcut which helps create the most common REST routes for a collection endpoint and its associated element.

A collection endpoint (e.g., /accounts) supports list (GET) and create (POST) actions. The collection's element (e.g., /accounts/:accounts_id) supports delete (DELETE), read (GET), patch (PATCH), and update (PUT) actions.

By default, every HTTP request to a collection's element is routed through a resource_lookup action (see "under" in Mojolicious::Routes::Route). This helps reduce the process of looking up a collection's resource to a single location. See "SYNOPSIS" for an example of its use.

options

The following options allow a collection to be fine-tuned.

collection_method_map
  $r->collection(
    'invoices',
    {
      collection_method_map => {
        get  => 'list',
        post => 'create',
        # delete => 'delete_collection',  # delete all-the-things!
        # put    => 'update_collection'   # update all-the-things!
      }
    }
  );

The above represents the default HTTP method mappings for collections. It's possible to change the mappings globally (when importing the plugin) or per collection (as above).

These HTTP method mappings only apply to the collection. e.g., /invoices. Please see element_method_map if you want to apply different HTTP mappings to an element like /invoices/:id.

controller
  # collection doesn't build a namespace for subroutes by default
  my $accounts = $r->collection('accounts');    # MyApp::Controller::Accounts
  $accounts->collection('invoices');            # MyApp::Controller::Invoices

  # collection can build namespaces, but can be difficult to keep track of. Use
  # the restify helper if namespaces are important to you.
  #
  # MyApp::Controller::Accounts
  my $accounts = $r->collection('accounts');
  # MyApp::Controller::Accounts::Invoices
  my $invoices = $accounts->collection('invoices', controller => 'accounts');
  # MyApp::Controller::Accounts::Invoices::Foo
  $invoices->collection('foo', controller => 'accounts-invoices');

Prepends the controller name (which is automatically generated based on the path name) with this option value if present. Used internally by "restify" to build a perlish namespace from the paths. "collection" does not build a namespace by default.

element
  # GET,POST                      /messages     200
  # DELETE,GET,PATCH,PUT,UPDATE   /messages/1   200
  $r->collection('messages');     # element routes are created by default

  # GET,POST                      /messages     200
  # DELETE,GET,PATCH,PUT,UPDATE   /messages/1   404
  $r->collection('messages', element => 0);

Enables or disables chaining an element to the collection. Disabling the element portion of a collection means that only the create and list actions will be created.

element_method_map
  $r->collection(
    'invoices',
    {
      element_method_map  => {
        'delete' => 'delete',
        'get'    => 'read',
        'patch'  => 'patch',
        'put'    => 'update',
      }
    }
  );

The above represents the default HTTP method mappings. It's possible to change the mappings globally (when importing the plugin) or per collection (as above).

These HTTP method mappings only apply to the collection's element. e.g., /invoices/:id.

over
  $r->collection('invoices', over => 'int');
  $r->collection('invoices', over => 'standard');
  $r->collection('accounts', over => 'uuid');

Allows a collection's element to be restricted to a specific data type using Mojolicious' route conditions. "int", "standard" and "uuid" are added automatically if they don't already exist.

placeholder
  # /versions/:versions_id      { versions_id => '123'}
  # /versions/#versions_id      { versions_id => '123.00'}
  # /versions/*versions_id      { versions_id => '123.00/1'}

The placeholder is used to capture the element id within a route. It can be one of standard ':', relaxed '#' or wildcard '*'. You might need to adjust the placholder option in certain scenarios, but the standard placeholder should suffice for most normal REST endpoints.

  $r->collection('/messages', placeholder => ':');

Elements are chained to a collection using the standard placeholder by default. They match all characters except / and .. See "Standard-placeholders" in Mojolicious::Guides::Routing.

  $r->collection('/relaxed-messages', placeholder => '#');

Placeholders can be relaxed, matching all characters expect /. Useful if you need to capture a domain name within a route. See "Relaxed-placeholders" in Mojolicious::Guides::Routing.

  $r->collection('/wildcard-messages', placeholder => '*');

Or they can be greedy, matching everything, inclusive of / and .. Useful if you need to capture everything within a route. See "Wildcard-placeholders" in Mojolicious::Guides::Routing.

prefix
  # without a prefix
  $r->collection('invoices');
  say $c->url_for('invoices', invoices_id => 1);

  # with a prefix
  $r->collection('invoices', prefix => 'v1');
  say $c->url_for('v1_invoices', invoices_id => 1);

Adds a prefix to the automatically generated route name for each collection and element action.

resource_lookup
  $r->collection('nolookup', resource_lookup => 0);

Enables or disables adding a resource_lookup action to the element of the collection.

element

  my $r    = $self->routes;
  my $news = $r->get('/news')->to('foo#news');
  $news->element('news');

A Mojolicious route shortcut called internally by "collection" to add the element routes to a collection. You shouldn't need to call this shortcut directly.

When an element is added to a collection's route, the resource ID is captured using a standard placeholder by default.

CREDITS

In alphabetical order:

    Castaway

    Dragoș-Robert Neagu

    Toratora

COPYRIGHT AND LICENSE

Copyright (C) 2015-2017, Paul Williams.

This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.

AUTHOR

Paul Williams <kwakwa@cpan.org>

SEE ALSO

Mojolicious, Mojolicious::Plugin::REST, Mojolicious::Plugin::RESTRoutes.