File Coverage

blib/lib/WebAPI/DBIC/Resource/ActiveModel/Role/DBIC.pm
Criterion Covered Total %
statement 12 93 12.9
branch 0 24 0.0
condition 0 12 0.0
subroutine 4 13 30.7
pod 0 6 0.0
total 16 148 10.8


line stmt bran cond sub pod time code
1             package WebAPI::DBIC::Resource::ActiveModel::Role::DBIC;
2             $WebAPI::DBIC::Resource::ActiveModel::Role::DBIC::VERSION = '0.004001';
3              
4 2     2   37225465 use Carp qw(croak confess);
  2         18  
  2         308  
5 2     2   1109 use Devel::Dwarn;
  2         18868  
  2         20  
6 2     2   1321 use JSON::MaybeXS qw(JSON);
  2         1640  
  2         108  
7              
8 2     2   935 use Moo::Role;
  2         39451  
  2         11  
9              
10              
11             requires 'get_url_for_item_relationship';
12             requires 'render_item_as_plain_hash';
13             requires 'path_for_item';
14             requires 'add_params_to_url';
15             requires 'prefetch';
16             requires 'type_namer';
17              
18              
19              
20             sub activemodel_type {
21 0     0 0   my ($self) = @_;
22 0           return $self->type_namer->type_name_for_resultset($self->set);
23             }
24              
25              
26              
27              
28             sub render_activemodel_prefetch_rel {
29 0     0 0   my ($self, $set, $parent_relname, $relname, $rel_sets, $item_edit_rel_hooks) = @_;
30              
31 0           my $parent_class = $set->result_class;
32 0   0       my $child_class = $parent_class->relationship_info($relname)->{class} || die "panic";
33              
34 0           my @idcolumns = $child_class->unique_constraint_columns('primary'); # XXX wrong
35 0 0         if (@idcolumns > 1) { # eg many-to-many that doesn't have a separate id
36 0 0         warn "Child result class $child_class has multiple keys (@idcolumns) so relations like $relname won't have links generated.\n"
37             unless our $warn_once->{"$child_class $relname"}++;
38 0           return;
39             }
40              
41 0           my $rel_typename = $self->type_namer->type_name_for_result_class($child_class);
42              
43 0 0         return if $item_edit_rel_hooks->{$parent_relname}->{$relname};
44              
45             $item_edit_rel_hooks->{$parent_relname}->{$relname} = sub {
46 0     0     my ($activemodel_obj, $row) = @_;
47              
48 0           my $subitem = $row->$relname();
49              
50 0   0       my $rel_set = $rel_sets->{$rel_typename} ||= {};
51              
52 0           my $rel_ids;
53 0 0         if (not defined $subitem) {
    0          
    0          
54 0           $rel_ids = undef;
55             }
56             elsif ($subitem->isa('DBIx::Class::ResultSet')) { # one-to-many rel
57 0           $rel_ids = [];
58 0           while (my $subrow = $subitem->next) {
59 0           my $id = $subrow->id;
60 0           push @$rel_ids, $id;
61             my $rel_object = $self->render_row_as_activemodel_resource_object($subrow, undef, sub {
62 0           my ($activemodel_obj, $row) = @_;
63 0           $_->($activemodel_obj, $row) for values %{$item_edit_rel_hooks->{$relname}};
  0            
64 0           });
65             # In case this object has been pulled in before, do what we can
66             # to preserve the existing keys and add to them as appropriate.
67 0   0       $rel_set->{$id} //= {};
68 0           $rel_set->{$id} = { %{$rel_object}, %{$rel_set->{$id}} };
  0            
  0            
69             }
70             }
71             elsif ($subitem->isa('DBIx::Class::Row')) { # one-to-one rel
72 0           $rel_ids = $subitem->id;
73             my $rel_object = $self->render_row_as_activemodel_resource_object($subitem, undef, sub {
74 0           my ($activemodel_obj, $row) = @_;
75 0           $_->($activemodel_obj, $row) for values %{$item_edit_rel_hooks->{$relname}};
  0            
76 0           });
77             # In case this object has been pulled in before, do what we can
78             # to preserve the existing keys and add to them as appropriate.
79 0   0       $rel_set->{$subitem->id} //= {};
80 0           $rel_set->{$subitem->id} = { %{$rel_object}, %{$rel_set->{$subitem->id}} };
  0            
  0            
81             }
82             else {
83 0           die "panic: don't know how to handle $row $relname value $subitem";
84             }
85              
86             # XXX We could either create a 'relationship_namer' similar to the 'type_namer',
87             # or create a mechanism to facilitate adapter/serializer classes.
88             # Per http://emberjs.com/api/data/classes/DS.ActiveModelAdapter.html:
89             # This should use the relationship name, singularized, and suffixed with
90             # '_id' for belongsTo relationships or '_ids' for hasMany relationships.
91 0 0         if ($rel_ids) {
92 0 0         my $suffix = ref($rel_ids) ? '_ids' : '_id';
93 0           my $relname_id = Lingua::EN::Inflect::Number::to_S($relname).$suffix;
94 0           $activemodel_obj->{$relname_id} = $rel_ids;
95             }
96             }
97 0           }
98              
99              
100             sub render_activemodel_response { # return top-level document hashref
101 0     0 0   my ($self) = @_;
102              
103 0           my $set = $self->set;
104 0           my $prefetch = $self->prefetch;
105              
106 0           my $rel_sets = {};
107 0           my $item_edit_rel_hooks = {};
108              
109             $self->traverse_prefetch($set, [ 'top' ], $prefetch, sub {
110 0     0     my ($self, $set, $parent_rel, $prefetch) = @_;
111             #warn "$self: $set, $parent_rel, $prefetch\n";
112 0           $self->render_activemodel_prefetch_rel($set, $parent_rel->[-1], $prefetch, $rel_sets, $item_edit_rel_hooks)
113 0           });
114              
115 0           my $result_class = $set->result_class;
116             my $set_data = $self->render_set_as_array_of_activemodel_resource_objects($set, undef, sub {
117 0     0     my ($activemodel_obj, $row) = @_;
118 0           $_->($activemodel_obj, $row) for values %{$item_edit_rel_hooks->{'top'}};
  0            
119 0           });
120              
121             # construct top document to return
122 0 0         my $top_set_key = ($self->param('distinct')) ? 'data' : $self->activemodel_type;
123 0           my $top_doc = { # http://jsonapi.org/format/#document-structure-top-level
124             $top_set_key => $set_data,
125             };
126              
127 0 0         if (keys %$rel_sets) {
128 0           while ( my ($k, $v) = each %$rel_sets) {
129             # sort just for test stability,
130 0           $top_doc->{$k} = [ @{$v}{ sort keys %$v } ];
  0            
131             }
132             }
133              
134 0           my $total_items;
135 0 0 0       if (($self->param('with')||'') =~ /count/) { # XXX
136 0           $total_items = $set->pager->total_entries;
137 0           $top_doc->{meta}{count} = $total_items; # XXX detail not in spec
138             }
139              
140 0           return $top_doc;
141             }
142              
143              
144              
145             sub render_item_as_activemodel_hash {
146 0     0 0   my ($self, $item) = @_;
147              
148 0           my $data = $self->render_item_as_plain_hash($item);
149              
150 0           return $data;
151             }
152              
153              
154             sub render_set_as_array_of_activemodel_resource_objects {
155 0     0 0   my ($self, $set, $render_method, $edit_hook) = @_;
156              
157 0           my @activemodel_objs;
158 0           while (my $row = $set->next) {
159 0           push @activemodel_objs, $self->render_row_as_activemodel_resource_object($row, $render_method, $edit_hook);
160             }
161              
162 0           return \@activemodel_objs;
163             }
164              
165             sub render_row_as_activemodel_resource_object {
166 0     0 0   my ($self, $row, $render_method, $edit_hook) = @_;
167 0   0       $render_method ||= 'render_item_as_activemodel_hash';
168              
169 0           my $obj = $self->$render_method($row);
170 0 0         $edit_hook->($obj, $row) if $edit_hook;
171              
172 0           return $obj;
173             }
174              
175              
176              
177              
178              
179             1;
180              
181             __END__
182              
183             =pod
184              
185             =encoding UTF-8
186              
187             =head1 NAME
188              
189             WebAPI::DBIC::Resource::ActiveModel::Role::DBIC
190              
191             =head1 VERSION
192              
193             version 0.004001
194              
195             =head1 NAME
196              
197             WebAPI::DBIC::Resource::ActiveModel::Role::DBIC - a role with core methods for DBIx::Class resources
198              
199             =head1 AUTHOR
200              
201             Tim Bunce <Tim.Bunce@pobox.com>
202              
203             =head1 COPYRIGHT AND LICENSE
204              
205             This software is copyright (c) 2015 by Tim Bunce.
206              
207             This is free software; you can redistribute it and/or modify it under
208             the same terms as the Perl 5 programming language system itself.
209              
210             =cut