File Coverage

blib/lib/Adapter/Async/Model.pm
Criterion Covered Total %
statement 83 99 83.8
branch 21 36 58.3
condition 5 10 50.0
subroutine 16 18 88.8
pod n/a
total 125 163 76.6


line stmt bran cond sub pod time code
1             package Adapter::Async::Model;
2             $Adapter::Async::Model::VERSION = '0.018';
3 1     1   32988 use strict;
  1         3  
  1         43  
4 1     1   7 use warnings;
  1         3  
  1         57  
5              
6             =head1 NAME
7              
8             Adapter::Async::Model - helper class for defining models
9              
10             =head1 VERSION
11              
12             version 0.018
13              
14             =head1 DESCRIPTION
15              
16             Generates accessors and helpers for code which interacts with L-related
17             classes. Please read the warnings in L before continuing.
18              
19             All definitions are applied via the L method:
20              
21             package Some::Class;
22             use Adapter::Async::Model {
23             some_thing => 'string',
24             some_array => {
25             collection => 'OrderedList',
26             type => '::Thing',
27             }
28             };
29              
30             Note that methods are applied via a UNITCHECK block by default.
31              
32             =cut
33              
34 1     1   765 use Log::Any qw($log);
  1         30349  
  1         5  
35              
36 1     1   10295 use Future;
  1         15658  
  1         61  
37              
38 1     1   1251 use Module::Load;
  1         1521  
  1         8  
39 1     1   66 use Data::Dumper;
  1         2  
  1         103  
40 1     1   745 use Variable::Disposition qw(retain_future);
  1         1943  
  1         428  
41              
42             =head2 import
43              
44             =over 4
45              
46             =item * defer_methods - if true (default), this will delay creation of methods such as C using a UNITCHECK block, pass defer_methods => 0 to disable this and create the methods immediately
47              
48             =item * model_base - the base class to prepend when types are specified with a leading ::
49              
50             =back
51              
52             =cut
53              
54             my %defined;
55              
56             sub import {
57 5     5   280 my ($class, $def, %args) = @_;
58 5         14 my $pkg = caller;
59             # No definition? Then we're probably just doing a module-load test, nothing
60             # for us to do here
61 5 100       87 return unless $def;
62              
63 4         8 $defined{$pkg} = 1;
64 4 50       12 $args{defer_methods} = 1 unless exists $args{defer_methods};
65 4 50       43 ($args{model_base} = $pkg) =~ s/Model\K.*// unless exists $args{model_base};
66              
67             my $type_expand = sub {
68 7     7   12 my ($type) = @_;
69 7 100       25 return unless defined $type;
70 4 50       18 $type = $args{model_base} . $type if substr($type, 0, 2) eq '::';
71 4         9 $type
72 4         22 };
73              
74 4         6 my %loader;
75              
76             my @methods;
77 4         19 for my $k (keys %$def) {
78 5         8 my $details = $def->{$k};
79 5 100       18 $details = { type => $details } unless ref $details;
80 5         6 my $code;
81 5         22 my %collection_class_for = (
82             UnorderedMap => 'Adapter::Async::UnorderedMap::Hash',
83             OrderedList => 'Adapter::Async::OrderedList::Array',
84             );
85 5 50       15 if(defined(my $from = $details->{from})) {
86 0         0 $log->tracef("Should apply field %s from %s for %s", $k, $from, $pkg);
87 0         0 ++$loader{$_} for grep /::/, map $type_expand->($_), @{$details}{qw(type)};
  0         0  
88             } else {
89 1     1   9 no strict 'refs';
  1         2  
  1         71  
90 1     1   165 no warnings 'once';
  1         3  
  1         899  
91 3         18 push @{$pkg . '::attrs'}, $k unless $details->{collection}
92 5 100       15 }
93              
94 5 100       13 if(my $type = $details->{collection}) {
95 2   50     10 my $collection_class = $collection_class_for{$type} // die "unknown collection $type";
96 2         6 ++$loader{$collection_class};
97 2         16 $log->tracef("%s->%s collection: %s", $pkg, $k, $type);
98 2         36 ++$loader{$_} for grep /::/, map $type_expand->($_), @{$details}{qw(key item)};
  2         11  
99             $code = sub {
100 6     6   3612 my $self = shift;
101 6 50       26 die "no args expected" if @_;
102 6   66     80 $self->{$k} //= $collection_class->new;
103             }
104 2         11 } else {
105 3   50     15 my $type = $type_expand->($details->{type} // die "unknown type in package $pkg - " . Dumper($def));
106 3 50       10 ++$loader{$type} if $type =~ /::/;
107              
108 3         15 $log->tracef("%s->%s scalar %s", $pkg, $k, $type);
109             $code = sub {
110 4     4   1443 my ($self) = shift;
111 4 50       48 return $self->{$k} unless @_;
112 0         0 $self->{$k} = shift;
113 0         0 return $self
114             }
115 3         56 }
116              
117 5         21 push @methods, $k => $code;
118             }
119              
120             push @methods, new => sub {
121 4     4   5111 my ($class) = shift;
122 4         19 my $self = bless { @_ }, $class;
123 4 50       50 $self->init if $self->can('init');
124 4         14 $self
125 4         39 };
126             push @methods, get_or_create => sub {
127 0     0   0 my ($self, $type, $v, $create) = @_;
128 0 0       0 return Future->done($v) if ref $v;
129             retain_future(
130             $self->$type->exists($v)->then(sub {
131 0 0   0   0 return $self->$type->get_key($v) if shift;
132              
133 0         0 my $item = $create->($v);
134 0         0 $log->tracef("Set %s on %s for %s to %s via %s", $v, $type, "$self", $item, ''.$self->$type);
135             $self->$type->set_key(
136             $v => $item
137             )->transform(
138 0         0 done => sub { $item }
139             )
140 0         0 })
141 0         0 )
142 4         33 };
143              
144 4         20 for(sort keys %loader) {
145 2         10 $log->tracef("Loading %s for %s", $_, $pkg);
146 2 50 33     65 Module::Load::load($_) unless exists($defined{$_}) || $_->can('new')
147             }
148              
149             my $apply_methods = sub {
150 4     4   31 while(my ($k, $code) = splice @methods, 0, 2) {
151 1     1   25 no strict 'refs';
  1         3  
  1         266  
152 13 50       149 if($pkg->can($k)) {
153 0         0 $log->tracef("Not creating method %s for %s since it exists already", $k, $pkg);
154             } else {
155 13         17 *{$pkg . '::' . $k} = $code;
  13         3048  
156             }
157             }
158 4         87 };
159              
160 4 50       23 if($args{defer_methods}) {
161 0         0 require Check::UnitCheck;
162 0         0 Check::UnitCheck::unitcheckify($apply_methods);
163             } else {
164 4         14 $apply_methods->();
165             }
166             }
167              
168             1;
169              
170             __END__