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.019';
3 1     1   20547 use strict;
  1         2  
  1         53  
4 1     1   4 use warnings;
  1         2  
  1         40  
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   608 use Log::Any qw($log);
  1         10935  
  1         6  
35              
36 1     1   4012 use Future;
  1         10795  
  1         38  
37              
38 1     1   526 use Module::Load;
  1         830  
  1         5  
39 1     1   495 use Data::Dumper;
  1         5023  
  1         54  
40 1     1   339 use Variable::Disposition qw(retain_future);
  1         929  
  1         233  
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   174 my ($class, $def, %args) = @_;
58 5         6 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       46 return unless $def;
62              
63 4         6 $defined{$pkg} = 1;
64 4 50       9 $args{defer_methods} = 1 unless exists $args{defer_methods};
65 4 50       32 ($args{model_base} = $pkg) =~ s/Model\K.*// unless exists $args{model_base};
66              
67             my $type_expand = sub {
68 7     7   7 my ($type) = @_;
69 7 100       14 return unless defined $type;
70 4 50       9 $type = $args{model_base} . $type if substr($type, 0, 2) eq '::';
71 4         5 $type
72 4         13 };
73              
74 4         2 my %loader;
75              
76             my @methods;
77 4         12 for my $k (keys %$def) {
78 5         6 my $details = $def->{$k};
79 5 100       11 $details = { type => $details } unless ref $details;
80 5         3 my $code;
81 5         10 my %collection_class_for = (
82             UnorderedMap => 'Adapter::Async::UnorderedMap::Hash',
83             OrderedList => 'Adapter::Async::OrderedList::Array',
84             );
85 5 50       11 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   5 no strict 'refs';
  1         1  
  1         35  
90 1     1   17 no warnings 'once';
  1         3  
  1         503  
91 3         11 push @{$pkg . '::attrs'}, $k unless $details->{collection}
92 5 100       6 }
93              
94 5 100       7 if(my $type = $details->{collection}) {
95 2   50     8 my $collection_class = $collection_class_for{$type} // die "unknown collection $type";
96 2         3 ++$loader{$collection_class};
97 2         6 $log->tracef("%s->%s collection: %s", $pkg, $k, $type);
98 2         4 ++$loader{$_} for grep /::/, map $type_expand->($_), @{$details}{qw(key item)};
  2         6  
99             $code = sub {
100 6     6   967 my $self = shift;
101 6 50       14 die "no args expected" if @_;
102 6   66     42 $self->{$k} //= $collection_class->new;
103             }
104 2         5 } else {
105 3   50     6 my $type = $type_expand->($details->{type} // die "unknown type in package $pkg - " . Dumper($def));
106 3 50       7 ++$loader{$type} if $type =~ /::/;
107              
108 3         9 $log->tracef("%s->%s scalar %s", $pkg, $k, $type);
109             $code = sub {
110 4     4   473 my ($self) = shift;
111 4 50       24 return $self->{$k} unless @_;
112 0         0 $self->{$k} = shift;
113 0         0 return $self
114             }
115 3         12 }
116              
117 5         12 push @methods, $k => $code;
118             }
119              
120             push @methods, new => sub {
121 4     4   1405 my ($class) = shift;
122 4         9 my $self = bless { @_ }, $class;
123 4 50       23 $self->init if $self->can('init');
124 4         7 $self
125 4         11 };
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         9 };
143              
144 4         10 for(sort keys %loader) {
145 2         3 $log->tracef("Loading %s for %s", $_, $pkg);
146 2 50 33     30 Module::Load::load($_) unless exists($defined{$_}) || $_->can('new')
147             }
148              
149             my $apply_methods = sub {
150 4     4   24 while(my ($k, $code) = splice @methods, 0, 2) {
151 1     1   13 no strict 'refs';
  1         1  
  1         146  
152 13 50       63 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         10 *{$pkg . '::' . $k} = $code;
  13         1332  
156             }
157             }
158 4         27 };
159              
160 4 50       7 if($args{defer_methods}) {
161 0         0 require Check::UnitCheck;
162 0         0 Check::UnitCheck::unitcheckify($apply_methods);
163             } else {
164 4         5 $apply_methods->();
165             }
166             }
167              
168             1;
169              
170             __END__