File Coverage

blib/lib/Mojolicious/Plugin/DBIC/Controller/DBIC.pm
Criterion Covered Total %
statement 60 78 76.9
branch 18 30 60.0
condition 5 11 45.4
subroutine 7 9 77.7
pod 4 4 100.0
total 94 132 71.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::DBIC::Controller::DBIC;
2             our $VERSION = '0.003';
3             # ABSTRACT: Build simple views to DBIC data
4              
5             #pod =head1 SYNOPSIS
6             #pod
7             #pod use Mojolicious::Lite;
8             #pod plugin DBIC => { schema => ... };
9             #pod get '/', {
10             #pod controller => 'DBIC',
11             #pod action => 'list',
12             #pod resultset => 'BlogPosts',
13             #pod template => 'blog/list',
14             #pod };
15             #pod
16             #pod =head1 DESCRIPTION
17             #pod
18             #pod This controller allows for easy working with data from the schema.
19             #pod Controllers are configured through the stash when setting up the routes.
20             #pod
21             #pod =head1 SEE ALSO
22             #pod
23             #pod L
24             #pod
25             #pod =cut
26              
27 2     2   32087 use Mojo::Base 'Mojolicious::Controller';
  2         7  
  2         13  
28              
29             #pod =method list
30             #pod
31             #pod get '/', {
32             #pod controller => 'DBIC',
33             #pod action => 'list',
34             #pod resultset => 'BlogPosts',
35             #pod template => 'blog/list',
36             #pod };
37             #pod
38             #pod List data in a ResultSet. Returns false if it has rendered a response,
39             #pod true if dispatch can continue.
40             #pod
41             #pod This method uses the following stash values for configuration:
42             #pod
43             #pod =over
44             #pod
45             #pod =item resultset
46             #pod
47             #pod The L class to list.
48             #pod
49             #pod =back
50             #pod
51             #pod This method sets the following stash values for template rendering:
52             #pod
53             #pod =over
54             #pod
55             #pod =item resultset
56             #pod
57             #pod The L object containing the desired objects.
58             #pod
59             #pod =back
60             #pod
61             #pod =cut
62              
63             sub list {
64 2     2 1 839 my ( $c ) = @_;
65 2         12 my $rs_class = $c->stash( 'resultset' );
66 2         37 my $rs = $c->schema->resultset( $rs_class );
67 2         923 return $c->stash(
68             resultset => $rs,
69             );
70             }
71              
72             #pod =method get
73             #pod
74             #pod get '/blog/:id', {
75             #pod controller => 'DBIC',
76             #pod action => 'get',
77             #pod resultset => 'BlogPosts',
78             #pod template => 'blog/get',
79             #pod };
80             #pod
81             #pod Fetch a single result by its ID. If no result is found, renders a not
82             #pod found error. Returns false if it has rendered a response, true if
83             #pod dispatch can continue.
84             #pod
85             #pod This method uses the following stash values for configuration:
86             #pod
87             #pod =over
88             #pod
89             #pod =item resultset
90             #pod
91             #pod The L class to use.
92             #pod
93             #pod =item id
94             #pod
95             #pod The ID to pass to L.
96             #pod
97             #pod =back
98             #pod
99             #pod This method sets the following stash values for template rendering:
100             #pod
101             #pod =over
102             #pod
103             #pod =item row
104             #pod
105             #pod The L object containing the desired object.
106             #pod
107             #pod =back
108             #pod
109             #pod =cut
110              
111             sub get {
112 4     4 1 91773 my ( $c ) = @_;
113 4         16 my $rs_class = $c->stash( 'resultset' );
114 4         50 my $id = $c->stash( 'id' );
115 4         46 my $rs = $c->schema->resultset( $rs_class );
116 4         1679 my $row = $rs->find( $id );
117 4 100       13350 if ( !$row ) {
118 2         37 $c->reply->not_found;
119 2         132379 return;
120             }
121 2         38 return $c->stash(
122             row => $row,
123             );
124             }
125              
126             #pod =method set
127             #pod
128             #pod $routes->any( [ 'GET', 'POST' ] => '/:id/edit' )->to(
129             #pod 'DBIC#set',
130             #pod resultset => $resultset_name,
131             #pod template => $template_name,
132             #pod );
133             #pod
134             #pod $routes->any( [ 'GET', 'POST' ] => '/create' )->to(
135             #pod 'DBIC#set',
136             #pod resultset => $resultset_name,
137             #pod template => $template_name,
138             #pod forward_to => $route_name,
139             #pod );
140             #pod
141             #pod This route creates a new item or updates an existing item in
142             #pod a collection. If the user is making a C request, they will simply
143             #pod be shown the template. If the user is making a C or C
144             #pod request, the form parameters will be read, and the user will either be
145             #pod shown the form again with the result of the form submission (success or
146             #pod failure) or the user will be forwarded to another place.
147             #pod
148             #pod This method uses the following stash values for configuration:
149             #pod
150             #pod =over
151             #pod
152             #pod =item resultset
153             #pod
154             #pod The resultset to use. Required.
155             #pod
156             #pod =item id
157             #pod
158             #pod The ID of the item from the collection. Optional: If not specified, a new
159             #pod item will be created. Usually part of the route path as a placeholder.
160             #pod
161             #pod =item template
162             #pod
163             #pod The name of the template to use. See L
164             #pod for how template names are resolved.
165             #pod
166             #pod =item forward_to
167             #pod
168             #pod The name of a route to forward the user to on success. Optional. Any
169             #pod route placeholders that match item field names will be filled in.
170             #pod
171             #pod $routes->get( '/:id/:slug' )->name( 'blog.view' );
172             #pod $routes->post( '/create' )->to(
173             #pod 'DBIC#set',
174             #pod resultset => 'blog',
175             #pod template => 'blog_edit.html.ep',
176             #pod forward_to => 'blog.view',
177             #pod );
178             #pod
179             #pod # { id => 1, slug => 'first-post' }
180             #pod # forward_to => '/1/first-post'
181             #pod
182             #pod Forwarding will not happen for JSON requests.
183             #pod
184             #pod =item properties
185             #pod
186             #pod Restrict this route to only setting the given properties. An array
187             #pod reference of properties to allow. Trying to set additional properties
188             #pod will result in an error.
189             #pod
190             #pod B Unless restricted to certain properties using this
191             #pod configuration, this method accepts all valid data configured for the
192             #pod collection. The data being submitted can be more than just the fields
193             #pod you make available in the form. If you do not want certain data to be
194             #pod written through this form, you can prevent it by using this.
195             #pod
196             #pod =back
197             #pod
198             #pod The following stash values are set by this method:
199             #pod
200             #pod =over
201             #pod
202             #pod =item row
203             #pod
204             #pod The L that is being edited, if the C is given.
205             #pod Otherwise, the item that was created.
206             #pod
207             #pod =item error
208             #pod
209             #pod A scalar containing the exception thrown by the insert/update.
210             #pod
211             #pod =back
212             #pod
213             #pod Each field in the item is also set as a param using
214             #pod L so that tag helpers like C
215             #pod will be pre-filled with the values. See
216             #pod L for more information. This also means
217             #pod that fields can be pre-filled with initial data or new data by using GET
218             #pod query parameters.
219             #pod
220             #pod This method is protected by L
221             #pod (CSRF) protection|Mojolicious::Guides::Rendering/Cross-site request
222             #pod forgery>. CSRF protection prevents other sites from tricking your users
223             #pod into doing something on your site that they didn't intend, such as
224             #pod editing or deleting content. You must add a C<< <%= csrf_field %> >> to
225             #pod your form in order to delete an item successfully. See
226             #pod L.
227             #pod
228             #pod Displaying a form could be done as a separate route using the C
229             #pod method, but with more code:
230             #pod
231             #pod $routes->get( '/:id/edit' )->to(
232             #pod 'DBIC#get',
233             #pod resultset => $resultset_name,
234             #pod template => $template_name,
235             #pod );
236             #pod $routes->post( '/:id/edit' )->to(
237             #pod 'DBIC#set',
238             #pod resultset => $resultset_name,
239             #pod template => $template_name,
240             #pod );
241             #pod
242             #pod =cut
243              
244             sub set {
245 6     6 1 91991 my ( $c ) = @_;
246 6   50     25 my $rs_class = $c->stash( 'resultset' )
247             || die q{"resultset" name not defined in stash};
248 6         84 my $id = $c->stash( 'id' );
249              
250             # Display the form, if requested. This makes the simple case of
251             # displaying and managing a form easier with a single route instead
252             # of two routes (one to "yancy#get" and one to "yancy#set")
253 6 100       65 if ( $c->req->method eq 'GET' ) {
254 4 100       68 if ( $id ) {
255 2         9 my $row = $c->schema->resultset( $rs_class )->find( $id );
256 2         6558 $c->stash( row => $row );
257 2         74 my @props = $row->result_source->columns;
258 2         36 for my $key ( @props ) {
259             # Mojolicious TagHelpers take current values through the
260             # params, but also we allow pre-filling values through the
261             # GET query parameters (except for passwords)
262 6   66     793 $c->param( $key => $c->param( $key ) // $row->$key );
263             }
264             }
265              
266             $c->respond_to(
267 4         267 json => {
268             status => 400,
269             json => {
270             errors => [
271             {
272             message => 'GET request for JSON invalid',
273             },
274             ],
275             },
276             },
277             html => { },
278             );
279 4         17805 return;
280             }
281              
282 2 50 33     46 if ( $c->accepts( 'html' ) && $c->validation->csrf_protect->has_error( 'csrf_token' ) ) {
283 0         0 $c->app->log->error( 'CSRF token validation failed' );
284 0 0       0 $c->render(
285             status => 400,
286             row => $id ? $c->schema->resultset( $rs_class )->find( $id ) : undef,
287             errors => [
288             {
289             message => 'CSRF token invalid.',
290             },
291             ],
292             );
293 0         0 return;
294             }
295              
296 2         2835 my $data = $c->req->params->to_hash;
297 2         95 delete $data->{csrf_token};
298             #; use Data::Dumper;
299             #; $c->app->log->debug( Dumper $data );
300              
301 2         14 my $rs = $c->schema->resultset( $rs_class );
302 2 50       965 if ( my $props = $c->stash( 'properties' ) ) {
303             $data = {
304 0         0 map { $_ => $data->{ $_ } }
305 0         0 grep { exists $data->{ $_ } }
  0         0  
306             @$props
307             };
308             }
309              
310 2         29 my $row;
311 2 100       7 my $update = $id ? 1 : 0;
312 2 100       8 if ( $update ) {
313 1         5 $row = $rs->find( $id );
314 1         2965 eval { $row->update( $data ) };
  1         21  
315             }
316             else {
317 1         2 $row = eval { $rs->create( $data ) };
  1         5  
318             }
319              
320 2 50       4904 if ( my $error = $@ ) {
321 0         0 $c->app->log->error( 'Error in set: ' . $error );
322 0         0 $c->res->code( 500 );
323 0 0       0 $row = $id ? $rs->find( $id ) : undef;
324 0         0 $c->respond_to(
325             json => { json => { error => $error } },
326             html => { row => $row, error => $error },
327             );
328 0         0 return;
329             }
330              
331             return $c->respond_to(
332             json => sub {
333 0 0   0   0 $c->stash(
334             status => $update ? 200 : 201,
335             json => $row->get_inflated_columns,
336             );
337             },
338             html => sub {
339 2 50   2   938 if ( my $route = $c->stash( 'forward_to' ) ) {
340 2         43 $c->redirect_to( $route, $row->get_inflated_columns );
341 2         2497 return;
342             }
343 0         0 $c->stash( row => $row );
344             },
345 2         24 );
346             }
347              
348             #pod =method delete
349             #pod
350             #pod $routes->any( [ 'GET', 'POST' ], '/delete/:id' )->to(
351             #pod 'DBIC#delete',
352             #pod resultset => $resultset_name,
353             #pod template => $template_name,
354             #pod forward_to => $route_name,
355             #pod );
356             #pod
357             #pod This route deletes a row from a ResultSet. If the user is making
358             #pod a C request, they will simply be shown the template (which can be
359             #pod used to confirm the delete). If the user is making a C or C
360             #pod request, the row will be deleted and the user will either be shown the
361             #pod form again with the result of the form submission (success or failure)
362             #pod or the user will be forwarded to another place.
363             #pod
364             #pod This method uses the following stash values for configuration:
365             #pod
366             #pod =over
367             #pod
368             #pod =item resultset
369             #pod
370             #pod The ResultSet class to use. Required.
371             #pod
372             #pod =item id
373             #pod
374             #pod The ID of the row from the table. Required. Usually part of the
375             #pod route path as a placeholder.
376             #pod
377             #pod =item template
378             #pod
379             #pod The name of the template to use. See L
380             #pod for how template names are resolved.
381             #pod
382             #pod =item forward_to
383             #pod
384             #pod The name of a route to forward the user to on success. Optional.
385             #pod Forwarding will not happen for JSON requests.
386             #pod
387             #pod =back
388             #pod
389             #pod The following stash values are set by this method:
390             #pod
391             #pod =over
392             #pod
393             #pod =item row
394             #pod
395             #pod The row that will be deleted. If displaying the form again after the row
396             #pod is deleted, this will be C.
397             #pod
398             #pod =back
399             #pod
400             #pod This method is protected by L
401             #pod (CSRF) protection|Mojolicious::Guides::Rendering/Cross-site request
402             #pod forgery>. CSRF protection prevents other sites from tricking your users
403             #pod into doing something on your site that they didn't intend, such as
404             #pod editing or deleting content. You must add a C<< <%= csrf_field %> >> to
405             #pod your form in order to delete an item successfully. See
406             #pod L.
407             #pod
408             #pod =cut
409              
410             sub delete {
411 2     2 1 30439 my ( $c ) = @_;
412 2         8 my $rs_class = $c->stash( 'resultset' );
413 2         26 my $id = $c->stash( 'id' );
414 2         23 my $rs = $c->schema->resultset( $rs_class );
415 2         782 my $row = $rs->find( $id );
416              
417             # Display the form, if requested. This makes it easy to display
418             # a confirmation page in a single route.
419 2 100       5659 if ( $c->req->method eq 'GET' ) {
420 1         29 $c->respond_to(
421             json => {
422             status => 400,
423             json => {
424             errors => [
425             {
426             message => 'GET request for JSON invalid',
427             },
428             ],
429             },
430             },
431             html => { row => $row },
432             );
433 1         5341 return;
434             }
435              
436 1 50 33     25 if ( $c->accepts( 'html' ) && $c->validation->csrf_protect->has_error( 'csrf_token' ) ) {
437 0         0 $c->app->log->error( 'CSRF token validation failed' );
438 0         0 $c->render(
439             status => 400,
440             row => $row,
441             errors => [
442             {
443             message => 'CSRF token invalid.',
444             },
445             ],
446             );
447 0         0 return;
448             }
449              
450 1         1386 $row->delete;
451              
452             return $c->respond_to(
453             json => sub {
454 0     0   0 $c->rendered( 204 );
455 0         0 return;
456             },
457             html => sub {
458 1 50   1   458 if ( my $route = $c->stash( 'forward_to' ) ) {
459 1         17 $c->redirect_to( $route );
460 1         936 return;
461             }
462             },
463 1         1930 );
464             }
465              
466             1;
467              
468             __END__