File Coverage

blib/lib/EntityModel/Resolver.pm
Criterion Covered Total %
statement 19 48 39.5
branch 2 12 16.6
condition 1 3 33.3
subroutine 4 9 44.4
pod 1 1 100.0
total 27 73 36.9


line stmt bran cond sub pod time code
1             package EntityModel::Resolver;
2             {
3             $EntityModel::Resolver::VERSION = '0.102';
4             }
5 1     1   682 use EntityModel::Class;
  1         2  
  1         7  
6              
7             =head2 import
8              
9             Create the helper functions in the caller's namespace.
10              
11             Takes the following named parameters:
12              
13             =over 4
14              
15             =item * model - the model to use for resolving entities
16              
17             =back
18              
19             =cut
20              
21             sub import {
22 1     1   5 my $class = shift;
23 1         3 my %args = @_;
24 1   33     9 my $model = $args{model} || EntityModel->default_model;
25 1         3 my $pkg = (caller)[0];
26              
27             # Now we have a better idea of what we're doing, call through
28             # to the various import helpers to do the real work
29 1         6 $class->import_resolve(package => $pkg, model => $model);
30             }
31              
32             =head2 import_resolve
33              
34             Takes the following named parameters:
35              
36             =over 4
37              
38             =item * package - the package to install this helper function into
39              
40             =item * model - the model to use for resolving entities
41              
42             =back
43              
44             Returns
45              
46             =cut
47              
48             {
49             my %active_resolutions;
50             sub import_resolve {
51 1     1 1 2 my $class = shift;
52 1         4 my %args = @_;
53 1 50       5 my $pkg = $args{package} or die "No package provided";
54 1 50       5 my $model = $args{model} or die "No model provided";
55             my $code = sub (&;@) {
56 0     0   0 my $request = shift;
57 0         0 my $target = shift;
58 0 0       0 my ($storage) = $model->storage->list or die "No storage found";
59             # We'll build up the results in an array...
60 0         0 my @rslt;
61             # ... and a list of pending tasks so we can wait on them once we have
62             # a clear idea of what we're waiting for.
63             my @futures;
64              
65             # So the first step is to pull our list of key,value pairs from the
66             # coderef, then we can go through those to start pulling data.
67 0         0 my @pending = $request->();
68 0         0 while(@pending) {
69             # We expect (entity_name, keyfield_value) pairs
70 0         0 my ($k, $v) = splice @pending, 0, 2;
71              
72             # Helps if we have an entity with a valid keyfield:
73 0 0       0 my $entity = $model->entity_by_name($k) or die "Entity [$k] not found";
74 0 0       0 die "No keyfield for $entity" unless defined $entity->keyfield;
75              
76             # Stash a placeholder value at the appropriate place in the output array,
77             # and prepare a future to update it when we know what the real value is.
78             # TODO Something about this just doesn't sit right with me.
79 0         0 push @rslt, undef;
80 0         0 my $idx = $#rslt;
81             my $future = Future->new->on_ready(sub {
82 0     0   0 ($rslt[$idx]) = shift->get;
83 0         0 });
84              
85             # Common handler to update our future when we have the value either through insert or find
86 0     0   0 my $handler = sub { $future->done(shift) };
  0         0  
87              
88             # Start with a lookup
89 0         0 my $attempt = 0;
90 0         0 my $search_party; $search_party = sub {
91 0 0   0   0 die "This is hopeless, we've already tried $attempt times and we're not getting anywhere" if ++$attempt > 3;
92             $storage->find(
93             entity => $entity,
94             on_item => $handler,
95             on_not_found => sub {
96             # Someone else might have created between our failed lookup and our
97             # creation attempt, so be prepared for this ->create call to fail
98             # as well.
99 0         0 $storage->create(
100             entity => $entity,
101             data => { $entity->keyfield => $v, },
102             on_complete => $handler,
103             # If we fail for some reason, loop around and try again
104             on_failure => $search_party,
105             );
106             }
107 0         0 );
108 0         0 };
109             # Do the lookup and add to our queue
110 0         0 $search_party->();
111 0         0 push @futures, $future;
112             }
113              
114             # We need to keep the master future around until we've finished processing,
115             # so we'll stash locally and clean up on completion.
116 0         0 my $future = Future->needs_all(@futures);
117 0         0 my $key = "$future";
118             $future->on_ready(sub {
119 0     0   0 delete $active_resolutions{$key};
120 0         0 $target->(@rslt);
121 0         0 });
122 0         0 $active_resolutions{$key} = $future;
123 1         7 };
124 1     1   1056 { no strict 'refs'; *{join '::', $pkg, 'resolve'} = $code }
  1         2  
  1         91  
  1         2  
  1         1  
  1         2645  
125             }
126             }
127              
128             1;
129