File Coverage

lib/Mojolicious/Plugin/RESTRoutes.pm
Criterion Covered Total %
statement 37 38 97.3
branch 8 10 80.0
condition 4 5 80.0
subroutine 5 5 100.0
pod 1 1 100.0
total 55 59 93.2


line stmt bran cond sub pod time code
1 1     1   308724 use Modern::Perl; # strict, warnings etc.;
  1         1429  
  1         5  
2             package Mojolicious::Plugin::RESTRoutes;
3             # ABSTRACT: routing helper for RESTful operations
4             # VERSION
5             $Mojolicious::Plugin::RESTRoutes::VERSION = '0.010011';
6 1     1   133 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 L<REST|http://en.wikipedia.org/wiki/Representational_state_transfer>ful
14             #pod L<CRUD|http://en.wikipedia.org/wiki/Create,_read,_update_and_delete>
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   1442 use Lingua::EN::Inflect qw/PL/;
  1         15202  
  1         603  
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 14461 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 $userroute = $self->rest_routes(name => 'user');
36             #pod
37             #pod # Installs the following routes (given that $r->namespaces == ['My::Mojo']):
38             #pod # GET /api/users --> My::Mojo::User::rest_list()
39             #pod # POST /api/users --> My::Mojo::User::rest_create()
40             #pod # GET /api/users/:userid --> My::Mojo::User::rest_show()
41             #pod # PUT /api/users/:userid --> My::Mojo::User::rest_update()
42             #pod # DELETE /api/users/:userid --> My::Mojo::User::rest_remove()
43             #pod
44             #pod I<Please note>: plural forms will be used in the route, i.e. "users" instead of "user".
45             #pod
46             #pod You can also chain C<rest_routes>:
47             #pod
48             #pod $userroute->rest_routes(name => 'hat', readonly => 1);
49             #pod
50             #pod # Installs the following additional routes:
51             #pod # GET /api/users/:userid/hats --> My::Mojo::Hat::rest_list()
52             #pod # GET /api/users/:userid/hats/:hatid --> My::Mojo::Hat::rest_show()
53             #pod
54             #pod The target controller has to implement the following methods:
55             #pod
56             #pod =for :list
57             #pod * C<rest_list>
58             #pod * C<rest_create>
59             #pod * C<rest_show>
60             #pod * C<rest_update>
61             #pod * C<rest_remove>
62             #pod
63             #pod B<Parameters to control the route creation>
64             #pod
65             #pod =over
66             #pod
67             #pod =item name
68             #pod
69             #pod The name of the resource, e.g. a "user", a "book" etc. This name will be used to
70             #pod build the route URL as well as the controller name (see example above).
71             #pod
72             #pod =item readonly (optional)
73             #pod
74             #pod If set to 1, no create/update/delete routes will be created
75             #pod
76             #pod =item controller (optional)
77             #pod
78             #pod Default behaviour is to use the resource name to build the CamelCase controller
79             #pod name (this is done by L<Mojolicious::Routes::Route>). You can change this by
80             #pod directly specifying the controller's name via the I<controller> attribute.
81             #pod
82             #pod Note that you have to give the real controller class name (i.e. CamelCased or
83             #pod whatever you class name looks like) including the full namespace.
84             #pod
85             #pod $self->rest_routes(name => 'user', controller => 'My::Mojo::Person');
86             #pod
87             #pod # Installs the following routes:
88             #pod # GET /api/users --> My::Mojo::Person::rest_list()
89             #pod # ...
90             #pod
91             #pod =back
92             #pod
93             #pod B<How to retrieve the parameters / IDs>
94             #pod
95             #pod There are two ways to retrieve the IDs given by the client in your C<rest_show>,
96             #pod C<rest_update> and C<rest_remove> methods.
97             #pod
98             #pod Example request: C<GET /api/users/5/hats/no9>
99             #pod
100             #pod 1. New way: the stash entry C<fm.ids> holds a hash with all ids:
101             #pod
102             #pod package My::Mojo::Hats;
103             #pod use Mojo::Base 'Mojolicious::Controller';
104             #pod
105             #pod sub rest_show {
106             #pod use Data::Dump qw(dump);
107             #pod print dump($self->stash('fm.ids'));
108             #pod
109             #pod # { user => 5, hat => 'no9' }
110             #pod }
111             #pod
112             #pod 2. Old way: for each resource there will be a parameter C<***id>, e.g.:
113             #pod
114             #pod package My::Mojo::Hat;
115             #pod use Mojo::Base 'Mojolicious::Controller';
116             #pod
117             #pod sub rest_show {
118             #pod my ($self) = @_;
119             #pod my $user = $self->param('userid');
120             #pod my $hat = $self->param('hatid');
121             #pod return $self->render(text => "$userid, $hatid");
122             #pod
123             #pod # text: "5, no9"
124             #pod }
125             #pod
126             #pod Furthermore, the parameter C<idname> holds the name of the last ID in the route:
127             #pod
128             #pod package My::Mojo::Hat;
129             #pod use Mojo::Base 'Mojolicious::Controller';
130             #pod
131             #pod sub rest_show {
132             #pod my $p_name = $self->param('idname');
133             #pod my $id = $self->param($p_name);
134             #pod return $self->render(text => sprintf("%s = %s", $p_name, $id || ''));
135             #pod
136             #pod # text: "hatid = 5"
137             #pod }
138             #pod
139             #pod =cut
140             # For the following TODOs also see http://pastebin.com/R9zXrtCg
141             # TODO Add GET /api/users/new --> rest_create_user_form
142             # TODO Add GET /api/users/:userid/edit --> rest_edit_user_form
143             # TODO Add GET /api/users/:userid/delete --> rest_delete_user_form
144             # TODO Add GET /api/users/search --> rest_search_user_form
145             # TODO Add PUT /api/users/search/:term --> rest_search_user_form (submit/execution)
146             $app->routes->add_shortcut(
147             rest_routes => sub {
148 3     3   761 my $r = shift;
149 3 50       18 my $params = { @_ ? (ref $_[0] ? %{ $_[0] } : @_) : () };
  0 50       0  
150              
151 3         6 my $name = $params->{name};
152 3   100     12 my $readonly = $params->{readonly} || 0;
153 3   66     10 my $controller = $params->{controller} || "$name#";
154            
155 3         14 my $plural = PL($name, 10);
156              
157 3         4353 $app->log->info("Creating REST routes for resource '$name' (controller: $controller)");
158            
159             #
160             # Generate "/$name" route, handled by controller $name
161             #
162 3         141 my $resource = $r->route("/$plural")->to($controller);
163            
164             # GET requests - lists the collection of this resource
165 3         1394 $resource->get->to('#rest_list')->name("list_$plural");
166 3         944 $app->log->debug("Created route GET ".$r->to_string."/$plural (rest_list)");
167            
168 3 100       469 if (!$readonly) {
169             # POST requests - creates a new resource
170 1         5 $resource->post->to('#rest_create')->name("create_$name");
171 1         341 $app->log->debug("Created route POST ".$r->to_string."/$plural (rest_create)");
172             };
173            
174             #
175             # Generate "/$name/:id" route, also handled by controller $name
176             #
177              
178             # resource routes might be chained, so we need to define an
179             # individual id and pass its name to the controller (idname)
180             $resource = $r
181             ->bridge("/$plural/:${name}id")
182             ->to(cb => sub {
183 10         458765 my ($c) = @_;
184 10         170 $c->app->log->debug(sprintf("Feeding ID into stash: \$c->stash('fm.ids')->{'%s'} = %s", $name, $c->param("${name}id")));
185 10 100       1077 $c->stash('fm.ids' => {}) unless $c->stash('fm.ids');
186 10         230 $c->stash('fm.ids')->{$name} = $c->param("${name}id");
187             })
188 3         131 ->route->to($controller, idname => "${name}id");
189            
190             # GET requests - lists a single resource
191 3         2028 $resource->get->to('#rest_show')->name("show_$name");
192 3         942 $app->log->debug("Created route GET ".$r->to_string."/$plural/:${name}id (rest_show)");
193            
194 3 100       493 if (!$readonly) {
195             # DELETE requests - deletes a resource
196 1         5 $resource->delete->to('#rest_remove')->name("delete_$name");
197 1         308 $app->log->debug("Created route DELETE ".$r->to_string."/$plural/:${name}id (rest_delete)");
198            
199             # PUT requests - updates a resource
200 1         124 $resource->put->to('#rest_update')->name("update_$name");
201 1         341 $app->log->debug("Created route PUT ".$r->to_string."/$plural/:${name}id (rest_update)");
202             }
203            
204             # return "/$name/:id" route so that potential child routes make sense
205 3         135 return $resource;
206             }
207 1         18 );
208             }
209              
210             1;
211              
212             __END__
213              
214             =pod
215              
216             =head1 NAME
217              
218             Mojolicious::Plugin::RESTRoutes - routing helper for RESTful operations
219              
220             =head1 VERSION
221              
222             version 0.010011
223              
224             =head1 DESCRIPTION
225              
226             This Mojolicious plugin adds a routing helper for
227             L<REST|http://en.wikipedia.org/wiki/Representational_state_transfer>ful
228             L<CRUD|http://en.wikipedia.org/wiki/Create,_read,_update_and_delete>
229             operations via HTTP to the app.
230              
231             The routes are intended, but not restricted to be used by AJAX applications.
232              
233             =head1 MOJOLICIOUS SHORTCUTS
234              
235             =head2 rest_routes
236              
237             Can be used to easily generate the needed RESTful routes for a resource.
238              
239             my $userroute = $self->rest_routes(name => 'user');
240              
241             # Installs the following routes (given that $r->namespaces == ['My::Mojo']):
242             # GET /api/users --> My::Mojo::User::rest_list()
243             # POST /api/users --> My::Mojo::User::rest_create()
244             # GET /api/users/:userid --> My::Mojo::User::rest_show()
245             # PUT /api/users/:userid --> My::Mojo::User::rest_update()
246             # DELETE /api/users/:userid --> My::Mojo::User::rest_remove()
247              
248             I<Please note>: plural forms will be used in the route, i.e. "users" instead of "user".
249              
250             You can also chain C<rest_routes>:
251              
252             $userroute->rest_routes(name => 'hat', readonly => 1);
253            
254             # Installs the following additional routes:
255             # GET /api/users/:userid/hats --> My::Mojo::Hat::rest_list()
256             # GET /api/users/:userid/hats/:hatid --> My::Mojo::Hat::rest_show()
257              
258             The target controller has to implement the following methods:
259              
260             =over 4
261              
262             =item *
263              
264             C<rest_list>
265              
266             =item *
267              
268             C<rest_create>
269              
270             =item *
271              
272             C<rest_show>
273              
274             =item *
275              
276             C<rest_update>
277              
278             =item *
279              
280             C<rest_remove>
281              
282             =back
283              
284             B<Parameters to control the route creation>
285              
286             =over
287              
288             =item name
289              
290             The name of the resource, e.g. a "user", a "book" etc. This name will be used to
291             build the route URL as well as the controller name (see example above).
292              
293             =item readonly (optional)
294              
295             If set to 1, no create/update/delete routes will be created
296              
297             =item controller (optional)
298              
299             Default behaviour is to use the resource name to build the CamelCase controller
300             name (this is done by L<Mojolicious::Routes::Route>). You can change this by
301             directly specifying the controller's name via the I<controller> attribute.
302              
303             Note that you have to give the real controller class name (i.e. CamelCased or
304             whatever you class name looks like) including the full namespace.
305              
306             $self->rest_routes(name => 'user', controller => 'My::Mojo::Person');
307              
308             # Installs the following routes:
309             # GET /api/users --> My::Mojo::Person::rest_list()
310             # ...
311              
312             =back
313              
314             B<How to retrieve the parameters / IDs>
315              
316             There are two ways to retrieve the IDs given by the client in your C<rest_show>,
317             C<rest_update> and C<rest_remove> methods.
318              
319             Example request: C<GET /api/users/5/hats/no9>
320              
321             1. New way: the stash entry C<fm.ids> holds a hash with all ids:
322              
323             package My::Mojo::Hats;
324             use Mojo::Base 'Mojolicious::Controller';
325              
326             sub rest_show {
327             use Data::Dump qw(dump);
328             print dump($self->stash('fm.ids'));
329            
330             # { user => 5, hat => 'no9' }
331             }
332              
333             2. Old way: for each resource there will be a parameter C<***id>, e.g.:
334              
335             package My::Mojo::Hat;
336             use Mojo::Base 'Mojolicious::Controller';
337              
338             sub rest_show {
339             my ($self) = @_;
340             my $user = $self->param('userid');
341             my $hat = $self->param('hatid');
342             return $self->render(text => "$userid, $hatid");
343            
344             # text: "5, no9"
345             }
346              
347             Furthermore, the parameter C<idname> holds the name of the last ID in the route:
348              
349             package My::Mojo::Hat;
350             use Mojo::Base 'Mojolicious::Controller';
351              
352             sub rest_show {
353             my $p_name = $self->param('idname');
354             my $id = $self->param($p_name);
355             return $self->render(text => sprintf("%s = %s", $p_name, $id || ''));
356            
357             # text: "hatid = 5"
358             }
359              
360             =head1 METHODS
361              
362             =head2 register
363              
364             Adds the routing helper. Is called by Mojolicious.
365              
366             =encoding utf8
367              
368             =head1 AUTHOR
369              
370             Jens Berthold <cpan-mp-restroutes@jebecs.de>
371              
372             =head1 COPYRIGHT AND LICENSE
373              
374             This software is Copyright (c) 2015 by Jens Berthold.
375              
376             This is free software, licensed under:
377              
378             The MIT (X11) License
379              
380             =cut