File Coverage

blib/lib/Abstract/Meta/Attribute.pm
Criterion Covered Total %
statement 75 79 94.9
branch 17 20 85.0
condition 9 19 47.3
subroutine 29 30 96.6
pod 23 23 100.0
total 153 171 89.4


line stmt bran cond sub pod time code
1             package Abstract::Meta::Attribute;
2              
3 5     5   29 use strict;
  5         7  
  5         159  
4 5     5   26 use warnings;
  5         7  
  5         130  
5 5     5   25 use Carp 'confess';
  5         7  
  5         256  
6 5     5   26 use base 'Abstract::Meta::Attribute::Method';
  5         10  
  5         3699  
7 5     5   54 use vars qw($VERSION);
  5         12  
  5         6850  
8              
9             $VERSION = 0.04;
10              
11             =head1 NAME
12              
13             Abstract::Meta::Attribute - Meta object attribute.
14              
15             =head1 SYNOPSIS
16              
17             use Abstract::Meta::Class ':all';
18             has '$.attr1' => (default => 0);
19              
20             =head1 DESCRIPTION
21              
22             An object that describes an attribute.
23             It includes required, data type, association validation, default value, lazy retrieval.
24             Name of attribute must begin with one of the follwoing prefix:
25             $. => Scalar,
26             @. => Array,
27             %. => Hash,
28             &. => Code,
29              
30              
31             =head1 EXPORT
32              
33             None.
34              
35             =head2 METHODS
36              
37             =over
38              
39             =item new
40              
41             =cut
42              
43              
44             sub new {
45 56     56 1 84 my $class = shift;
46 56         325 unshift @_, $class;
47 56         103 bless {&initialise}, $class;
48             }
49              
50              
51             =item initialise
52              
53             Initialises attribute
54              
55             =cut
56              
57             {
58             my %supported_type = (
59             '$' => 'Scalar',
60             '@' => 'Array',
61             '%' => 'Hash',
62             '&' => 'Code',
63             );
64              
65             sub initialise {
66 56     56 1 487 my ($class, %args) = @_;
67 56         159 foreach my $k (keys %args) {
68 242 50       2364 confess "unknown attribute $k"
69             unless Abstract::Meta::Attribute->can($k);
70             }
71 56 50       184 my $name = $args{name} or confess "name is requried";
72 56 100 50     218 my $storage_type = $args{storage_type} = $args{transistent} ? 'Hash' : $args{storage_type} || '';
73            
74 56         93 my $attribute_index = 0;
75 56 100       314 if($storage_type eq 'Array') {
76 2         6 my $meta_class= Abstract::Meta::Class::meta_class($args{class});
77 2         3 $attribute_index = $#{$meta_class->all_attributes} + 1;
  2         6  
78             }
79            
80 56         332 my ($type, $accessor_name) = ($name =~ /^([\$\@\%\&])\.(.*)$/);
81 56 50 0     270 confess "invalid attribute defintion ${class}::" .($accessor_name || $name) .", supported prefixes are \$.,%.,\@.,&."
      33        
82             if ! $type || ! $supported_type{$type};
83              
84 56         69 my %options;
85 56 100 100     656 $args{data_type_validation} = 1
      33        
86             if (! exists($args{data_type_validation})
87             && ($type eq '@' || $type eq '%' || $args{associated_class}));
88              
89 168         608 $options{'&.' . $_ } = $args{$_}
90 56         92 for grep {exists $args{$_}} (qw(on_read on_change on_validate));
91            
92            
93 56 100 66     301 my $storage_key = $storage_type eq 'Array' ? $attribute_index : $args{storage_key} || $args{name};
94              
95 56         121 $options{'$.name'} = $accessor_name;
96 56         100 $options{'$.storage_key'} = $storage_key;
97 56         118 $options{'$.mutator'} = "set_$accessor_name";
98 56         108 $options{'$.accessor'} = $accessor_name;
99 560         1646 $options{'$.' . $_ } = $args{$_}
100 56         170 for grep {exists $args{$_}}
101             (qw(class required default item_accessor associated_class data_type_validation index_by the_other_end transistent storage_type));
102            
103 56         138 $options{'$.perl_type'} = $supported_type{$type};
104 56 100       154 unless ($args{default}) {
105 48 100       156 if($type eq '%') {
    100          
106 9     8   52 $options{'$.default'} = sub{ {} };
  8         27  
107             } elsif ($type eq '@') {
108 9     9   45 $options{'$.default'} = sub { [] };
  9         31  
109             }
110             }
111 56         924 %options;
112             }
113             }
114              
115              
116             =item name
117              
118             Returns attribute name
119              
120             =cut
121              
122 87     87 1 191 sub name { shift()->{'$.name'} }
123              
124              
125             =item class
126              
127             Attribute's class name.
128              
129             =cut
130              
131 2     2 1 13 sub class { shift()->{'$.class'} }
132              
133              
134             =item storage_key
135              
136             Returns storage attribute key in object
137              
138             =cut
139              
140 252     252 1 616 sub storage_key { shift()->{'$.storage_key'} }
141              
142              
143              
144             =item perl_type
145              
146             Returns attribute type, Scalar, Hash, Array, Code
147              
148             =cut
149              
150 359     359 1 1178 sub perl_type { shift()->{'$.perl_type'} }
151              
152              
153             =item accessor
154              
155             Returns accessor name
156              
157             =cut
158              
159 332     332 1 1324 sub accessor { shift()->{'$.accessor'} }
160              
161              
162             =item mutator
163              
164             Returns mutator name
165              
166             =cut
167              
168 158     158 1 590 sub mutator { shift()->{'$.mutator'} }
169              
170              
171             =item required
172              
173             Returns required flag
174              
175             =cut
176              
177 165     165 1 648 sub required { shift()->{'$.required'} }
178              
179              
180             =item default
181              
182             Returns default value
183              
184             =cut
185              
186 130     130 1 542 sub default { shift()->{'$.default'} }
187              
188              
189             =item storage_type
190              
191             Hash|Array
192              
193             =cut
194              
195 295   50 295 1 1150 sub storage_type { shift()->{'$.storage_type'} ||= 'Hash' }
196              
197              
198             =item transistent
199              
200             If this flag is set, than storage of that attribte, will be force outside the object,
201             so you cant serialize that attribute,
202             It is especially useful when using callback, that cant be serialised (Storable dclone)
203             This option will generate cleanup and DESTORY methods.
204              
205             =cut
206              
207 374     374 1 4323 sub transistent { shift()->{'$.transistent'} }
208              
209              
210             =item item_accessor
211              
212             Returns name that will be used to construct the hash or array item accessor.
213             It will be used to retrieve or set array or hash item item
214              
215              
216             has '%.items' => (item_accessor => 'item');
217             ...
218             my $item_ref = $obj->items;
219             $obj->item(x => 3);
220             my $value = $obj->item('y')'
221              
222              
223             =cut
224              
225 65     65 1 306 sub item_accessor { shift()->{'$.item_accessor'} }
226              
227              
228              
229             =item associated_class
230              
231             Return name of the associated class.
232              
233             =cut
234              
235 245     245 1 1059 sub associated_class { shift()->{'$.associated_class'} }
236              
237              
238             =item index_by
239              
240             Name of the asscessor theat will return unique attribute for associated objects.
241             Only for toMany associaion, by deault uses objecy reference as index.
242              
243             package Class;
244             use Abstract::Meta::Class ':all';
245             has '$.name' => (required => 1);
246             has '%.details' => (
247             index_by => 'id',
248             item_accessor => 'detail',
249             );
250             my $obj = Class->
251              
252              
253              
254              
255             =cut
256              
257 133     133 1 416 sub index_by { shift()->{'$.index_by'} }
258              
259              
260             =item the_other_end
261              
262             Name of the asscessor/mutator on associated class to keep bideriectional association
263             This option will generate cleanup method.
264              
265             =cut
266              
267 87     87 1 210 sub the_other_end { shift()->{'$.the_other_end'} }
268              
269              
270             =item data_type_validation
271              
272             Flag that turn on/off data type validation.
273             Data type validation happens when using association_class or Array or Hash data type
274             unless you explicitly disable it by seting data_type_validation => 0.
275              
276             =cut
277              
278 58     58 1 314 sub data_type_validation { shift()->{'$.data_type_validation'} }
279              
280              
281             =item on_read
282              
283             Returns code reference that will be replace data read routine
284              
285             has '%.attrs.' => (
286             item_accessor => 'attr'
287             on_read => sub {
288             my ($self, $attribute, $scope, $key) = @_;
289             my $values = $attribute->get_values($self);
290             if ($scope eq 'accessor') {
291             return $values;
292             } else {
293             return $values->{$key};
294             }
295             },
296             );
297             has '@.array_attrs.' => (
298             item_accessor => 'array_item'
299             on_read => sub {
300             my ($self, $attribute, $scope, $index) = @_;
301             my $values = $attribute->get_values($self);
302             if ($scope eq 'accessor') {
303             return $values;
304             } else {
305             return $values->[$index];
306             }
307             },
308             );
309              
310             =cut
311              
312 76     76 1 182 sub on_read { shift()->{'&.on_read'} }
313              
314              
315             =item set_on_read
316              
317             Sets code reference that will be replace data read routine
318              
319             my $attr = MyClass->meta->attribute('attrs');
320             $attr->set_on_read(sub {
321             my ($self, $attribute, $scope, $key) = @_;
322             #do some stuff
323             });
324              
325             =cut
326              
327             sub set_on_read {
328 1     1 1 19 my ($attr, $value) = @_;
329 1         4 $attr->{'&.on_read'} = $value;
330 1         4 my $meta= $attr->class->meta;
331 1         6 $meta->install_attribute_methods($attr, 1);
332             }
333              
334              
335             =item on_change
336              
337             Code reference that will be executed when data is set,
338             Takes reference to the variable to be set.
339              
340             =cut
341              
342 72     72 1 160 sub on_change { shift()->{'&.on_change'} }
343              
344              
345              
346             =item set_on_change
347              
348             Sets code reference that will be executed when data is set,
349              
350             my $attr = MyClass->meta->attribute('attrs');
351             $attr->set_on_change(sub {
352             my ($self, $attribute, $scope, $value, $key) = @_;
353             if($scope eq 'mutator') {
354             my $hash = $$value;
355             foreach my $k (keys %$hash) {
356             # do some stuff
357             #$self->validate_trigger($k, $hash->{$k});
358             }
359             } else {
360             # do some stuff
361             $self->validate_trigger($key. $$value);
362             }
363             $self;
364             });
365              
366             =cut
367              
368             sub set_on_change {
369 0     0 1 0 my ($attr, $value) = @_;
370 0         0 $attr->{'&.on_change'} = $value;
371 0         0 my $meta= $attr->class->meta;
372 0         0 $meta->install_attribute_methods($attr, 1);
373             }
374              
375              
376              
377              
378              
379             =item on_validate
380              
381             Returns on validate code reference.
382             It is executed before the data type validation happens.
383              
384             =cut
385              
386 58     58 1 129 sub on_validate { shift()->{'&.on_validate'} }
387              
388              
389             =item set_on_validate
390              
391             Sets code reference that will be replace data read routine
392              
393             my $attr = MyClass->meta->attribute('attrs');
394             $attr->set_on_read(sub {
395             my ($self, $attribute, $scope, $key) = @_;
396             #do some stuff
397             });
398              
399             =cut
400              
401             sub set_on_validate {
402 1     1 1 24 my ($attr, $value) = @_;
403 1         3 $attr->{'&.on_validate'} = $value;
404 1         4 my $meta= $attr->class->meta;
405 1         5 $meta->install_attribute_methods($attr, 1);
406             }
407              
408              
409              
410              
411             1;
412              
413             __END__