File Coverage

blib/lib/WebAPI/DBIC/Resource/Role/ItemWritable.pm
Criterion Covered Total %
statement 9 46 19.5
branch 0 18 0.0
condition 0 4 0.0
subroutine 3 9 33.3
pod 0 3 0.0
total 12 80 15.0


line stmt bran cond sub pod time code
1             package WebAPI::DBIC::Resource::Role::ItemWritable;
2             $WebAPI::DBIC::Resource::Role::ItemWritable::VERSION = '0.003002';
3              
4 2     2   17162517 use Carp qw(croak confess);
  2         22  
  2         442  
5 2     2   1180 use Devel::Dwarn;
  2         19296  
  2         18  
6              
7 2     2   1431 use Moo::Role;
  2         42997  
  2         11  
8              
9              
10             requires 'render_item_into_body';
11             requires 'decode_json';
12             requires 'item';
13             requires 'param';
14             requires 'prefetch';
15             requires 'request';
16             requires 'response';
17             requires 'path_for_item';
18              
19              
20             # By default the DBIx::Class::Row update() call will only update the
21             # columns where %$hal contains different values to the ones in $item.
22             # This is usually a useful optimization but not always. So we provide
23             # a way to disable it on individual resources.
24             has skip_dirty_check => (
25             is => 'rw',
26             );
27              
28             has _pre_update_resource_method => (
29             is => 'rw',
30             );
31              
32             has content_types_accepted => (
33             is => 'lazy',
34             );
35              
36             sub _build_content_types_accepted {
37 0     0     return [ {'application/json' => 'from_plain_json'} ]
38             }
39              
40              
41             sub from_plain_json {
42 0     0 0   my $self = shift;
43 0           my $data = $self->decode_json( $self->request->content );
44 0           $self->update_resource($data, is_put_replace => 0);
45 0           return;
46             }
47              
48              
49             around 'allowed_methods' => sub {
50             my $orig = shift;
51             my $self = shift;
52            
53             my $methods = $self->$orig();
54              
55             $methods = [ qw(PUT DELETE), @$methods ] if $self->writable;
56              
57             return $methods;
58             };
59              
60              
61 0     0 0   sub delete_resource { return $_[0]->item->delete }
62              
63              
64             sub _do_update_resource {
65 0     0     my ($self, $item, $hal, $result_class) = @_;
66              
67             # provide a hook for richer behaviour, eg HAL
68 0           my $_pre_update_resource_method = $self->_pre_update_resource_method;
69 0 0         $self->$_pre_update_resource_method($item, $hal, $result_class)
70             if $_pre_update_resource_method;
71              
72             # By default the DBIx::Class::Row update() call below will only update the
73             # columns where %$hal contains different values to the ones in $item
74             # This is usually a useful optimization but not always. So we provide
75             # a way to disable it on individual resources.
76 0 0         if ($self->skip_dirty_check) {
77 0           $item->make_column_dirty($_) for keys %$hal;
78             }
79              
80             # Note that update() calls set_inflated_columns()
81 0           $item->update($hal);
82              
83             # XXX discard_changes causes a refetch of the record for prefetch
84             # perhaps worth trying to avoid the discard if not required
85 0           $item->discard_changes();
86              
87 0           return $item;
88             }
89              
90              
91             sub update_resource {
92 0     0 0   my ($self, $hal, %opts) = @_;
93 0           my $is_put_replace = delete $opts{is_put_replace};
94 0 0         croak "update_resource: invalid options: @{[ keys %opts ]}"
  0            
95             if %opts;
96              
97 0           my $schema = $self->item->result_source->schema;
98             # XXX perhaps the transaction wrapper belongs higher in the stack
99             # but it has to be below the auth layer which switches schemas
100             $schema->txn_do(sub {
101              
102 0     0     my $item;
103 0 0         if ($is_put_replace) {
104             # PUT == http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6
105              
106             # Using delete() followed by create() is a strict implementation
107             # of treating PUT on an item as a REPLACE, but it might not be ideal.
108             # Specifically it requires any FKs to be DEFERRED and it'll less
109             # efficient than a simple UPDATE. There's also a concern that if
110             # the REST API only has a partial view of the resource, ie not all
111             # columns, then do we want the original deleted if the 'hidden'
112             # fields can't be set?
113             # So this could me made optional on a per-resource-class basis,
114             # and/or via a request parameter.
115              
116             # we require PK fields to at least be defined
117             # XXX we ought to check that they match the URL since a PUT is
118             # required to store the entity "under the supplied Request-URI".
119             # XXX throw proper exception
120             defined $hal->{$_} or die "missing PK '$_'\n"
121 0   0       for $self->set->result_source->primary_columns;
122              
123 0           my $old_item = $self->item; # XXX might already be gone since the find()
124 0 0         $old_item->delete if $old_item; # XXX might already be gone since the find()
125              
126 0           my $links = delete $hal->{_links};
127 0           my $meta = delete $hal->{_meta};
128 0   0       my $embedded = delete $hal->{_embedded} && die "_embedded not supported here (yet?)\n";
129              
130 0           $item = $self->set->create($hal); # handles deflation
131              
132 0 0         $self->response->header('Location' => $self->path_for_item($item))
133             unless $old_item; # set Location and thus 201 if Created not modified
134             }
135             else {
136 0           $item = $self->_do_update_resource($self->item, $hal, $self->item->result_class);
137             }
138              
139 0           $self->item($item);
140              
141             # called here because create_path() is too late for WM
142             # and we need it to happen inside the transaction for rollback=1 to work
143             # XXX requires 'self' prefetch to get any others
144 0 0         $self->render_item_into_body() if grep {defined $_->{self}} @{$self->prefetch||[]};
  0 0          
  0            
145              
146 0 0         $schema->txn_rollback if $self->param('rollback'); # XXX
147 0           });
148 0           return;
149             }
150              
151             1;
152              
153             __END__
154              
155             =pod
156              
157             =encoding UTF-8
158              
159             =head1 NAME
160              
161             WebAPI::DBIC::Resource::Role::ItemWritable
162              
163             =head1 VERSION
164              
165             version 0.003002
166              
167             =head1 NAME
168              
169             WebAPI::DBIC::Resource::Role::ItemWritable - methods handling requests to update item resources
170              
171             =head1 AUTHOR
172              
173             Tim Bunce <Tim.Bunce@pobox.com>
174              
175             =head1 COPYRIGHT AND LICENSE
176              
177             This software is copyright (c) 2015 by Tim Bunce.
178              
179             This is free software; you can redistribute it and/or modify it under
180             the same terms as the Perl 5 programming language system itself.
181              
182             =cut