File Coverage

lib/Mojolicious/Plugin/RESTRoutes.pm
Criterion Covered Total %
statement 38 39 97.4
branch 9 12 75.0
condition 6 8 75.0
subroutine 5 5 100.0
pod 1 1 100.0
total 59 65 90.7


line stmt bran cond sub pod time code
1 1     1   262411 use Modern::Perl; # strict, warnings etc.;
  1         1360  
  1         5  
2             package Mojolicious::Plugin::RESTRoutes;
3             # ABSTRACT: routing helper for RESTful operations
4             # VERSION
5             $Mojolicious::Plugin::RESTRoutes::VERSION = '0.010012';
6 1     1   134 use Mojo::Base 'Mojolicious::Plugin';
  1         2  
  1         6  
7              
8             #pod =encoding utf8
9             #pod
10             #pod =head1 DESCRIPTION
11             #pod
12             #pod This Mojolicious plugin adds a routing helper for
13             #pod Lful
14             #pod L
15             #pod operations via HTTP to the app.
16             #pod
17             #pod The routes are intended, but not restricted to be used by AJAX applications.
18             #pod
19             #pod =cut
20              
21 1     1   1406 use Lingua::EN::Inflect qw/PL/;
  1         15256  
  1         565  
22              
23             #pod =method register
24             #pod
25             #pod Adds the routing helper. Is called by Mojolicious.
26             #pod
27             #pod =cut
28             sub register {
29 1     1 1 14086 my ($self, $app) = @_;
30            
31             #pod =mojo_short rest_routes
32             #pod
33             #pod Can be used to easily generate the needed RESTful routes for a resource.
34             #pod
35             #pod my $r = $self->routes;
36             #pod my $userroute = $r->rest_routes(name => 'user');
37             #pod
38             #pod # Installs the following routes (given that $r->namespaces == ['My::Mojo']):
39             #pod # GET /users --> My::Mojo::User::rest_list()
40             #pod # POST /users --> My::Mojo::User::rest_create()
41             #pod # GET /users/:userid --> My::Mojo::User::rest_show()
42             #pod # PUT /users/:userid --> My::Mojo::User::rest_update()
43             #pod # DELETE /users/:userid --> My::Mojo::User::rest_remove()
44             #pod
45             #pod I: the english plural form of the given C attribute will be
46             #pod used in the route, i.e. "users" instead of "user". If you want to specify
47             #pod another string, see parameter C below.
48             #pod
49             #pod You can also chain C:
50             #pod
51             #pod $userroute->rest_routes(name => 'hat', readonly => 1);
52             #pod
53             #pod # Installs the following additional routes:
54             #pod # GET /users/:userid/hats --> My::Mojo::Hat::rest_list()
55             #pod # GET /users/:userid/hats/:hatid --> My::Mojo::Hat::rest_show()
56             #pod
57             #pod The target controller has to implement the following methods:
58             #pod
59             #pod =for :list
60             #pod * C
61             #pod * C
62             #pod * C
63             #pod * C
64             #pod * C
65             #pod
66             #pod B
67             #pod
68             #pod =over
69             #pod
70             #pod =item name
71             #pod
72             #pod The name of the resource, e.g. a "user", a "book" etc. This name will be used to
73             #pod build the route URL as well as the controller name (see example above).
74             #pod
75             #pod =item readonly (optional)
76             #pod
77             #pod If set to 1, no create/update/delete routes will be created
78             #pod
79             #pod =item controller (optional)
80             #pod
81             #pod Default behaviour is to use the resource name to build the CamelCase controller
82             #pod name (this is done by L). You can change this by
83             #pod directly specifying the controller's name via the I attribute.
84             #pod
85             #pod Note that you have to give the real controller class name (i.e. CamelCased or
86             #pod whatever you class name looks like) including the full namespace.
87             #pod
88             #pod $r->rest_routes(name => 'user', controller => 'My::Mojo::Person');
89             #pod
90             #pod # Installs the following routes:
91             #pod # GET /users --> My::Mojo::Person::rest_list()
92             #pod # ...
93             #pod
94             #pod =item route (optional)
95             #pod
96             #pod Specify a name for the route, i.e. prevent automatic usage of english plural
97             #pod form of the C parameter as the route component.
98             #pod
99             #pod $r->rest_routes(name => 'angst', route => 'aengste');
100             #pod
101             #pod # Installs the following routes (given that $r->namespaces == ['My::Mojo']):
102             #pod # GET /aengste --> My::Mojo::Angst::rest_list()
103             #pod
104             #pod =back
105             #pod
106             #pod B
107             #pod
108             #pod There are two ways to retrieve the IDs given by the client in your C,
109             #pod C and C methods.
110             #pod
111             #pod Example request: C
112             #pod
113             #pod 1. New way: the stash entry C holds a hash with all ids:
114             #pod
115             #pod package My::Mojo::Hats;
116             #pod use Mojo::Base 'Mojolicious::Controller';
117             #pod
118             #pod sub rest_show {
119             #pod use Data::Dump qw(dump);
120             #pod print dump($self->stash('fm.ids'));
121             #pod
122             #pod # { user => 5, hat => 'no9' }
123             #pod }
124             #pod
125             #pod 2. Old way: for each resource there will be a parameter C<***id>, e.g.:
126             #pod
127             #pod package My::Mojo::Hat;
128             #pod use Mojo::Base 'Mojolicious::Controller';
129             #pod
130             #pod sub rest_show {
131             #pod my ($self) = @_;
132             #pod my $user = $self->param('userid');
133             #pod my $hat = $self->param('hatid');
134             #pod return $self->render(text => "$userid, $hatid");
135             #pod
136             #pod # text: "5, no9"
137             #pod }
138             #pod
139             #pod Furthermore, the parameter C holds the name of the last ID in the route:
140             #pod
141             #pod package My::Mojo::Hat;
142             #pod use Mojo::Base 'Mojolicious::Controller';
143             #pod
144             #pod sub rest_show {
145             #pod my $p_name = $self->param('idname');
146             #pod my $id = $self->param($p_name);
147             #pod return $self->render(text => sprintf("%s = %s", $p_name, $id || ''));
148             #pod
149             #pod # text: "hatid = 5"
150             #pod }
151             #pod
152             #pod =cut
153             # For the following TODOs also see http://pastebin.com/R9zXrtCg
154             # TODO Add GET /users/new --> rest_create_user_form
155             # TODO Add GET /users/:userid/edit --> rest_edit_user_form
156             # TODO Add GET /users/:userid/delete --> rest_delete_user_form
157             # TODO Add GET /users/search --> rest_search_user_form
158             # TODO Add PUT /users/search/:term --> rest_search_user_form (submit/execution)
159             $app->routes->add_shortcut(
160             rest_routes => sub {
161 4     4   798 my $r = shift;
162 4 50       22 my $params = { @_ ? (ref $_[0] ? %{ $_[0] } : @_) : () };
  0 50       0  
163              
164 4 50       11 my $name = $params->{name} or die "Parameter 'name' missing";
165 4   100     12 my $readonly = $params->{readonly} || 0;
166 4   66     11 my $controller = $params->{controller} || $name;
167 4   66     18 my $route_part = $params->{route} || PL($name, 10); # build english plural form
168              
169 4         4294 $app->log->info("Creating REST routes for resource '$name' (controller: $controller)");
170            
171             #
172             # Generate "/$name" route, handled by controller $name
173             #
174 4         164 my $resource = $r->route("/$route_part")->to(controller => $controller);
175            
176             # GET requests - lists the collection of this resource
177 4         1572 $resource->get->to('#rest_list')->name("list_$route_part");
178 4         1299 $app->log->debug("Created route GET ".$r->to_string."/$route_part (rest_list)");
179            
180 4 100       527 if (!$readonly) {
181             # POST requests - creates a new resource
182 1         5 $resource->post->to('#rest_create')->name("create_$name");
183 1         304 $app->log->debug("Created route POST ".$r->to_string."/$route_part (rest_create)");
184             };
185            
186             #
187             # Generate "/$name/:id" route, also handled by controller $name
188             #
189              
190             # resource routes might be chained, so we need to define an
191             # individual id and pass its name to the controller (idname)
192             $resource = $r
193             ->under("/$route_part/:${name}id" => sub {
194 10         398297 my ($c) = @_;
195 10         164 $c->app->log->debug(sprintf("Feeding ID into stash: \$c->stash('fm.ids')->{'%s'} = %s", $name, $c->param("${name}id")));
196 10 100       560 $c->stash('fm.ids' => {}) unless $c->stash('fm.ids');
197 10         201 $c->stash('fm.ids')->{$name} = $c->param("${name}id");
198 10         227 return 1;
199             })
200 4         147 ->to(controller => $controller, idname => "${name}id");
201            
202             # GET requests - lists a single resource
203 4         1997 $resource->get->to("#rest_show")->name("show_$name");
204 4         1283 $app->log->debug("Created route GET ".$r->to_string."/$route_part/:${name}id (rest_show)");
205            
206 4 100       522 if (!$readonly) {
207             # DELETE requests - deletes a resource
208 1         5 $resource->delete->to('#rest_remove')->name("delete_$name");
209 1         305 $app->log->debug("Created route DELETE ".$r->to_string."/$route_part/:${name}id (rest_delete)");
210            
211             # PUT requests - updates a resource
212 1         121 $resource->put->to('#rest_update')->name("update_$name");
213 1         302 $app->log->debug("Created route PUT ".$r->to_string."/$route_part/:${name}id (rest_update)");
214             }
215            
216             # return "/$name/:id" route so that potential child routes make sense
217 4         135 return $resource;
218             }
219 1         19 );
220             }
221              
222             1;
223              
224             __END__