File Coverage

blib/lib/Mandel/Relationship/ListOf.pm
Criterion Covered Total %
statement 27 111 24.3
branch 0 50 0.0
condition 0 20 0.0
subroutine 8 22 36.3
pod 1 1 100.0
total 36 204 17.6


line stmt bran cond sub pod time code
1             package Mandel::Relationship::ListOf;
2 1     1   546 use Mojo::Base 'Mandel::Relationship';
  1         2  
  1         6  
3 1     1   126 use Mojo::Util;
  1         2  
  1         36  
4 1     1   5 use Mango::BSON 'bson_dbref';
  1         2  
  1         1825  
5              
6             has push_method_name => sub { sprintf 'push_%s', shift->accessor };
7             has remove_method_name => sub { sprintf 'remove_%s', shift->accessor };
8             has search_method_name => sub { sprintf 'search_%s', shift->accessor };
9              
10             sub monkey_patch {
11 1     1 1 29 shift->_monkey_patch_all_method->_monkey_patch_push_method->_monkey_patch_remove_method->_monkey_patch_search_method;
12             }
13              
14             sub _monkey_patch_all_method {
15 1     1   2 my $self = shift;
16 1         5 my $accessor = $self->accessor;
17 1         10 my $search = $self->search_method_name;
18              
19             Mojo::Util::monkey_patch(
20             $self->document_class,
21             $accessor,
22             sub {
23 0     0   0 my ($doc, $cb) = @_;
        0      
24 0 0       0 my $cached = delete $doc->{fresh} ? undef : $doc->_cache($accessor);
25              
26             # Blocking
27 0 0       0 unless ($cb) {
28 0 0       0 return $cached if $cached;
29 0         0 my %lookup = map { $_->id, $_ } @{$doc->$search->all};
  0         0  
  0         0  
30 0 0       0 return $doc->_cache($accessor => [map { $lookup{$_->{'$id'}} } @{$doc->data->{$accessor} || []}]);
  0         0  
  0         0  
31             }
32              
33             # Non-blocking
34 0 0       0 if ($cached) {
35 0         0 $doc->$cb('', $cached);
36             }
37             else {
38             $doc->$search->all(
39             sub {
40 0     0   0 my ($collection, $err, $objs) = @_;
41 0         0 my %lookup = map { $_->id, $_ } @$objs;
  0         0  
42 0 0       0 $doc->$cb($err, $doc->_cache($accessor => [map { $lookup{$_->{'$id'}} } @{$doc->data->{$accessor} || []}]));
  0         0  
  0         0  
43             }
44 0         0 );
45             }
46              
47 0         0 return $doc;
48             }
49 1         14 );
50              
51 1         25 return $self;
52             }
53              
54             sub _monkey_patch_push_method {
55 1     1   3 my $self = shift;
56 1         2 my $accessor = $self->accessor;
57              
58             Mojo::Util::monkey_patch(
59             $self->document_class,
60             $self->push_method_name,
61             sub {
62 0 0   0   0 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
        0      
63 0         0 my ($doc, $obj, $pos) = @_;
64 0         0 my ($dbref, $push_ref, @update);
65 0         0 my $cached = $doc->_cache($accessor);
66              
67 0 0       0 if (ref $obj eq 'HASH') {
    0          
68 0         0 $obj = $self->_related_model->new_collection($doc->connection)->create($obj);
69             }
70             elsif (UNIVERSAL::isa($obj, 'Mango::BSON::ObjectID')) {
71 0         0 $obj = $self->_related_model->new_collection($doc->connection)->create({id => $obj});
72 0         0 $obj->_mark_stored_clean; # prevent save() from actually doing something below
73             }
74              
75 0         0 $dbref = bson_dbref $obj->model->collection_name, $obj->id;
76              
77 0 0       0 @update = (
78             {_id => $doc->id},
79             {'$push' => {$accessor => {'$each' => [$dbref], defined $pos ? ('$position' => $pos + 0) : ()}}},
80             {upsert => 1},
81             );
82              
83             $push_ref = sub {
84 0   0 0   0 my $list = $doc->data->{$accessor} ||= [];
85              
86 0 0 0     0 if (defined $pos and $pos < @$list) {
87 0 0       0 splice @$cached, $pos, 0, $obj if $cached;
88 0         0 splice @$list, $pos, 0, $dbref;
89             }
90             else {
91 0 0       0 push @$cached, $obj if $cached;
92 0         0 push @$list, $dbref;
93             }
94              
95 0         0 $doc->in_storage(1);
96 0         0 };
97              
98             # Blocking
99 0 0       0 unless ($cb) {
100 0         0 $obj->save;
101 0         0 $doc->_storage_collection->update(@update);
102 0         0 $push_ref->();
103 0         0 return $obj;
104             }
105              
106             # Non-blocking
107             Mojo::IOLoop->delay(
108             sub {
109 0     0   0 my ($delay) = @_;
110 0         0 $obj->save($delay->begin);
111 0         0 $doc->_storage_collection->update(@update, $delay->begin);
112             },
113             sub {
114 0     0   0 my ($delay, $o_err, $d_err, $updated) = @_;
115 0   0     0 my $err = $o_err || $d_err;
116 0 0 0     0 $err ||= 'Document was not stored. Unknown error' unless $updated and $updated->{n};
      0        
117 0 0       0 $push_ref->() unless $err;
118 0   0     0 $doc->$cb($err // '', $obj);
119             },
120 0         0 );
121              
122 0         0 return $doc;
123             }
124 1         6 );
125              
126 1         25 return $self;
127             }
128              
129             sub _monkey_patch_remove_method {
130 1     1   3 my $self = shift;
131 1         12 my $accessor = $self->accessor;
132              
133             Mojo::Util::monkey_patch(
134             $self->document_class,
135             $self->remove_method_name,
136             sub {
137 0     0   0 my ($doc, $obj, $cb) = @_;
        0      
138 0         0 my $cached = $doc->_cache($accessor);
139 0         0 my @update;
140              
141 0 0       0 unless (UNIVERSAL::isa($obj, 'Mandel::Document')) {
142 0         0 $obj = $self->_related_model->new_collection($doc->connection)->create({_id => $obj});
143             }
144              
145 0         0 @update = ({_id => $doc->id}, {'$pull' => {$accessor => bson_dbref($obj->model->collection_name, $obj->id)}});
146              
147             # Blocking
148 0 0       0 unless ($cb) {
149 0         0 $doc->_storage_collection->update(@update);
150 0 0       0 $doc->data->{$accessor} = [grep { $_->{'$id'} ne $obj->id } @{$doc->data->{$accessor} || []}];
  0         0  
  0         0  
151 0 0       0 @$cached = grep { $_->id ne $obj->id } @$cached if $cached;
  0         0  
152 0         0 return $doc;
153             }
154              
155             # Non-blocking
156             Mojo::IOLoop->delay(
157             sub {
158 0     0   0 my ($delay) = @_;
159 0         0 $doc->_storage_collection->update(@update, $delay->begin);
160             },
161             sub {
162 0     0   0 my ($delay, $err, $updated) = @_;
163 0 0       0 $doc->data->{$accessor} = [grep { $_->{'$id'} ne $obj->id } @{$doc->data->{$accessor} || []}] unless $err;
  0 0       0  
  0         0  
164 0 0 0     0 @$cached = grep { $_->id ne $obj->id } @$cached if $cached and !$err;
  0         0  
165 0         0 $doc->$cb($err);
166             },
167 0         0 );
168              
169 0         0 return $obj;
170             }
171 1         7 );
172              
173 1         23 return $self;
174             }
175              
176             sub _monkey_patch_search_method {
177 1     1   2 my $self = shift;
178 1         4 my $accessor = $self->accessor;
179              
180             Mojo::Util::monkey_patch(
181             $self->document_class,
182             $self->search_method_name,
183             sub {
184 0     0   0 my ($doc, $query, $extra) = @_;
        0      
185 0         0 my $related_model = $self->_related_model;
186              
187             return $related_model->new_collection(
188             $doc->connection,
189             extra => $extra || {},
190 0 0 0     0 query => {%{$query || {}}, _id => {'$in' => [map { $_->{'$id'} } @{$doc->data->{$accessor} || []}]}},
  0 0       0  
  0         0  
  0         0  
191             );
192             }
193 1         6 );
194              
195 1         27 return $self;
196             }
197              
198             1;
199              
200             =encoding utf8
201              
202             =head1 NAME
203              
204             Mandel::Relationship::ListOf - A field points to many other MongoDB documents
205              
206             =head1 DESCRIPTION
207              
208             L is a class used to describe the relationship
209             where one document has a list of DBRefs that point to other documents.
210             The connection between the documents is described in the database using
211             L.
212              
213             This relationship is EXPERIMENTAL. Let me of you are using it or don't like it.
214              
215             =head1 DATABASE STRUCTURE
216              
217             A "person" that has I "cats" will look like this in the database:
218              
219             mongodb> db.persons.find();
220             {
221             "_id" : ObjectId("5353ab13800fac3a0a8d5049"),
222             "kittens" : [
223             DBRef("cats", ObjectId("5353ab13c5483e16a1010000")),
224             DBRef("cats", ObjectId("5353ab13c5483e16a1020000"))
225             ]
226             }
227              
228             mongodb> db.cats.find();
229             { "_id" : ObjectId("5353ab13c5483e16a1010000") }
230             { "_id" : ObjectId("5353ab13c5483e16a1020000") }
231              
232             =head1 SYNOPSIS
233              
234             =head2 Using DSL
235              
236             package MyModel::Person;
237             use Mandel::Document;
238             list_of cats => 'MyModel::Cat';
239              
240             =head2 Using object oriented interface
241              
242             MyModel::Person->model->relationship(
243             "list_of",
244             "cats",
245             "MyModel::Cat",
246             );
247              
248             See also L.
249              
250             =head2 Methods generated
251              
252             # non-blocking
253             $person = MyModel::Person->new->push_cats($bson_oid, $pos, sub {
254             my($person, $err, $cat_obj) = @_;
255             # Note! This $cat_obj has only "id()" set
256             # ...
257             });
258              
259             Add the C<$bson_oid> to the "cats" list in C<$person>.
260              
261             $person = MyModel::Person->new->push_cats(\%constructor_args, $pos, sub {
262             my($person, $err, $cat_obj) = @_;
263             # ...
264             });
265              
266             Pushing a new cat with C<%constructor_args> will also insert a new cat object
267             into the database.
268              
269             $person = MyModel::Person->new->push_cats($cat_obj, $pos, sub {
270             my($person, $err, $cat_obj) = @_;
271             # ...
272             });
273              
274             C<$pos> is optional. When omitted, C will add the new element
275             to the end of list. See
276             L
277             for details.
278              
279             $person = MyModel::Cat->new->remove_cats($bson_oid, sub {
280             my($self, $err) = @_;
281             # Note! This $cat_obj has only "id()" set
282             });
283              
284             $person = MyModel::Cat->new->remove_cats($cat_obj, sub {
285             my($self, $err) = @_;
286             # ...
287             });
288              
289             Calling C will only remove the link, and not the related
290             object.
291              
292             $person = MyModel::Cat->new->cats(sub {
293             my($self, $err, $array_of_cats) = @_;
294             # ...
295             });
296              
297             Retrieve all the related cat objects.
298              
299             # blocking
300             $cat_obj = MyModel::Person->new->push_cats($bson_oid);
301             $cat_obj = MyModel::Person->new->push_cats(\%args);
302             $cat_obj = MyModel::Person->new->push_cats($cat_obj);
303             $person = MyModel::Person->new->remove_cats($bson_oid);
304             $person = MyModel::Person->new->remove_cats($cat_obj);
305             $array_of_cats = MyModel::Person->new->cats;
306              
307             $cat_collection = MyModel::Person->new->search_cats;
308              
309             Note! C does not guaranty the order of the results, like C
310             does.
311              
312             =head1 ATTRIBUTES
313              
314             L inherits all attributes from
315             L and implements the following new ones.
316              
317             =head2 push_method_name
318              
319             The name of the method used to add another document to the relationship.
320              
321             =head2 remove_method_name
322              
323             The name of the method used to remove an item from the list.
324              
325             =head2 search_method_name
326              
327             The name of the method used to search related documents.
328              
329             =head1 METHODS
330              
331             L inherits all methods from
332             L and implements the following new ones.
333              
334             =head2 monkey_patch
335              
336             Add methods to L.
337              
338             =head1 SEE ALSO
339              
340             L, L, L
341              
342             =head1 AUTHOR
343              
344             Jan Henning Thorsen - C
345              
346             =cut