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