File Coverage

blib/lib/Data/MultiValued/AttributeTrait.pm
Criterion Covered Total %
statement 69 77 89.6
branch 7 10 70.0
condition 3 12 25.0
subroutine 22 25 88.0
pod 13 13 100.0
total 114 137 83.2


line stmt bran cond sub pod time code
1             package Data::MultiValued::AttributeTrait;
2             {
3             $Data::MultiValued::AttributeTrait::VERSION = '0.0.1_4';
4             }
5             {
6             $Data::MultiValued::AttributeTrait::DIST = 'Data-MultiValued';
7             }
8 3     3   3108 use Moose::Role;
  3         9  
  3         26  
9 3     3   19302 use namespace::autoclean;
  3         8  
  3         33  
10 3     3   2599 use Data::MultiValued::AttributeAccessors;
  3         9  
  3         151  
11 3     3   23 use MooseX::Types::Moose qw(Str);
  3         6  
  3         39  
12 3     3   24440 use Try::Tiny;
  3         9  
  3         347  
13 3     3   20 use namespace::autoclean;
  3         7  
  3         36  
14              
15             # ABSTRACT: "base role" for traits of multi-valued Moose attributes
16              
17              
18             has 'full_storage_slot' => (
19             is => 'ro',
20             isa => Str,
21             lazy_build => 1,
22             );
23 7     7   336 sub _build_full_storage_slot { shift->name . '__MULTIVALUED_STORAGE__' }
24              
25              
26             my @accs_to_multiply=qw(accessor reader writer predicate clearer);
27              
28             for my $acc (@accs_to_multiply) {
29             has "multi_$acc" => (
30             is => 'ro',
31             isa => Str,
32             predicate => "has_multi_$acc",
33             );
34             }
35              
36              
37             requires 'multivalue_storage_class';
38              
39              
40             requires 'opts_to_pass_set';
41              
42              
43             requires 'opts_to_pass_get';
44              
45              
46             around slots => sub {
47             my ($orig, $self) = @_;
48             return ($self->$orig(), $self->full_storage_slot);
49             };
50              
51              
52             sub set_full_storage {
53 15     15 1 136 my ($self,$instance) = @_;
54              
55 15         66 my $ret = $self->multivalue_storage_class->new();
56 15         79 $self->associated_class->get_meta_instance->set_slot_value(
57             $instance,
58             $self->full_storage_slot,
59             $ret,
60             );
61 15         205 return $ret;
62             }
63              
64              
65             sub get_full_storage {
66 65     65 1 107 my ($self,$instance) = @_;
67              
68 65         378 return $self->associated_class->get_meta_instance
69             ->get_slot_value(
70             $instance,
71             $self->full_storage_slot,
72             );
73             }
74              
75              
76             sub full_storage {
77 59     59 1 104 my ($self,$instance) = @_;
78              
79 59   66     173 return $self->get_full_storage($instance)
80             || $self->set_full_storage($instance);
81             }
82              
83              
84 42     42 1 75979 sub accessor_metaclass { 'Data::MultiValued::AttributeAccessors' }
85              
86              
87             after install_accessors => sub {
88             my ($self) = @_;
89              
90             my $class = $self->associated_class;
91              
92             for my $meth (@accs_to_multiply) {
93             my $type = "multi_$meth";
94             my $check = "has_$meth";
95             my $multi_check = "has_$type";
96             next unless $self->$check || $self->$multi_check;
97              
98             my $name = $self->$type;
99             if (!$name) {
100             my $basename = $self->$meth;
101              
102             die 'MultiValued attribute trait is not compatible with subref accessors'
103             if ref($basename);
104              
105             $name = "${basename}_multi";
106             }
107              
108             $class->add_method(
109             $self->_process_accessors($type => $name,0)
110             );
111             }
112             };
113              
114             sub _filter_opts {
115 59     59   144 my ($hr,@fields) = @_;
116              
117 59         87 my %ret;
118 59         107 for my $f (@fields) {
119 73 100       243 if (exists $hr->{$f}) {
120 28         109 $ret{$f}=$hr->{$f};
121             }
122             }
123 59         184 return \%ret;
124             }
125              
126              
127             sub load_multi_value {
128 42     42 1 75 my ($self,$instance,$opts) = @_;
129              
130 42         260 my $opts_passed = _filter_opts($opts, $self->opts_to_pass_get);
131              
132 42         65 my $value;my $found=1;
  42         67  
133             try {
134 42     42   3024 $value = $self->full_storage($instance)->get($opts_passed);
135             }
136             catch {
137 8 50 33 8   9137 unless (ref($_) && $_->isa('Data::MultiValued::Exceptions::NotFound')) {
138 0         0 die $_;
139             }
140 8         69 $found = 0;
141 42         420 };
142              
143 42 100       1880 if ($found) {
144 34         239 $self->set_raw_value($instance,$value);
145             }
146             else {
147 8         35 $self->raw_clear_value($instance);
148             }
149             }
150              
151              
152             sub raw_clear_value {
153 8     8 1 18 my ($self,$instance) = @_;
154              
155 8         51 $self->associated_class->get_meta_instance
156             ->deinitialize_slot(
157             $instance,
158             $self->name,
159             );
160             }
161              
162              
163             sub store_multi_value {
164 17     17 1 29 my ($self,$instance,$opts) = @_;
165              
166 17         97 my $opts_passed = _filter_opts($opts, $self->opts_to_pass_set);
167              
168 17         113 $opts_passed->{value} = $self->get_raw_value($instance);
169              
170 17         492 $self->full_storage($instance)->set($opts_passed);
171             }
172              
173             our $dyn_opts = {};
174              
175              
176             before get_value => sub {
177             my ($self,$instance) = @_;
178              
179             $self->load_multi_value($instance,$dyn_opts);
180             };
181              
182              
183             sub get_multi_value {
184 26     26 1 62 my ($self,$instance,$opts) = @_;
185              
186 26         56 local $dyn_opts = $opts;
187              
188 26         166 return $self->get_value($instance);
189             }
190              
191              
192             after set_initial_value => sub {
193             my ($self,$instance,$value) = @_;
194              
195             $self->store_multi_value($instance,$dyn_opts);
196             };
197              
198              
199             after set_value => sub {
200             my ($self,$instance,$value) = @_;
201              
202             $self->store_multi_value($instance,$dyn_opts);
203             };
204              
205             sub set_multi_value {
206 6     6 1 18 my ($self,$instance,$opts,$value) = @_;
207              
208 6         12 local $dyn_opts = $opts;
209              
210 6         35 return $self->set_value($instance,$value);
211             }
212              
213              
214             before has_value => sub {
215             my ($self,$instance) = @_;
216              
217             $self->load_multi_value($instance,$dyn_opts);
218             };
219              
220             sub has_multi_value {
221 16     16 1 31 my ($self,$instance,$opts) = @_;
222              
223 16         32 local $dyn_opts = $opts;
224              
225 16         83 return $self->has_value($instance);
226             }
227              
228              
229             after clear_value => sub {
230             my ($self,$instance) = @_;
231              
232             $self->full_storage($instance)->clear($dyn_opts);
233             return;
234             };
235              
236             sub clear_multi_value {
237 0     0 1 0 my ($self,$instance,$opts) = @_;
238              
239 0         0 local $dyn_opts = $opts;
240              
241 0         0 return $self->clear_value($instance);
242             }
243              
244              
245             sub get_multi_read_method {
246 0     0 1 0 my $self = shift;
247 0   0     0 return $self->multi_reader || $self->multi_accessor
248             || $self->get_read_method . '_multi';
249             }
250              
251             sub get_multi_write_method {
252 0     0 1 0 my $self = shift;
253 0   0     0 return $self->multi_writer || $self->multi_accessor
254             || $self->get_write_method . '_multi';
255             }
256              
257              
258             sub _rebless_slot {
259 3     3   8 my ($self,$instance) = @_;
260              
261 3         13 my $st = $self->get_full_storage($instance);
262 3 50       25 return unless $st;
263              
264 3         67 bless $st, $self->multivalue_storage_class;
265 3         20 $st->_rebless_storage;
266             }
267              
268              
269             sub _as_hash {
270 3     3   6 my ($self,$instance) = @_;
271              
272 3         9 my $st = $self->get_full_storage($instance);
273 3 50       25 return unless $st;
274              
275 3         20 return $st->_as_hash;
276             }
277              
278             1;
279              
280             __END__
281             =pod
282              
283             =encoding utf-8
284              
285             =head1 NAME
286              
287             Data::MultiValued::AttributeTrait - "base role" for traits of multi-valued Moose attributes
288              
289             =head1 VERSION
290              
291             version 0.0.1_4
292              
293             =head1 DESCRIPTION
294              
295             Don't use this role directly, use
296             L<Data::MultiValued::AttributeTrait::Tags>,
297             L<Data::MultiValued::AttributeTrait::Ranges> or
298             L<Data::MultiValued::AttributeTrait::TagsAndRanges>.
299              
300             This role (together with L<Data::MultiValued::AttributeAccessors>)
301             defines all the basic plumbing to glue C<Data::MultiValued::Tags> etc
302             into Moose attributes.
303              
304             =head2 Implementation details
305              
306             The multi-value object is stored in the instance slot named by the
307             L</full_storage_slot> attribute attribute. C<before> modifiers on
308             getters load the appropriate value from the multi-value object into
309             the regular instance slot, C<after> modifiers on setters store the
310             value from the regular instance slot into the multi-value object.
311              
312             =head2 Attributes
313              
314             This trait adds some attributes to the attribute declarations in your
315             class. Example:
316              
317             has stuff => (
318             is => 'rw',
319             isa => 'Int',
320             traits => ['MultiValued::Tags'],
321             predicate => 'has_stuff',
322             multi_accessor => 'stuff_tagged',
323             multi_predicate => 'has_stuff_tagged',
324             );
325              
326             =head1 ATTRIBUTES
327              
328             =head2 C<full_storage_slot>
329              
330             The instance slot to use to store the C<Data::MultiValued::Tags> or
331             similar object. Defaults to C<"${name}__MULTIVALUED_STORAGE__">, where
332             C<$name> is the attribute name.
333              
334             =head2 C<multi_accessor>
335              
336             =head2 C<multi_reader>
337              
338             =head2 C<multi_writer>
339              
340             =head2 C<multi_predicate>
341              
342             =head2 C<multi_clearer>
343              
344             The names to use for the various additional accessors. See
345             L<Class::MOP::Attribute> for details. These default to
346             C<"${name}_multi"> where C<$name> is the name of the corresponding
347             non-multi accessor. So, for example,
348              
349             has stuff => (
350             is => 'rw',
351             traits => ['MultiValued::Tags'],
352             );
353              
354             will create a C<stuff> read / write accessor and a C<stuff_multi> read
355             / write tagged accessor.
356              
357             =head1 METHODS
358              
359             =head2 C<slots>
360              
361             Adds the L</full_storage_slot> to the list of used slots.
362              
363             =head2 C<set_full_storage>
364              
365             Stores a new instance of L</multivalue_storage_class> into the
366             L</full_storage_slot> of the instance.
367              
368             =head2 C<get_full_storage>
369              
370             Retrieves the value of the L</full_storage_slot> of the instance.
371              
372             =head2 C<full_storage>
373              
374             Returns an instance of L</multivalue_storage_class>, either by
375             retrieving it from the instance, or by creating one (and setting it in
376             the instance). Calls L</get_full_storage> and L</set_full_storage>.
377              
378             =head2 C<accessor_metaclass>
379              
380             Makes sure that all accessors for this attribute are created via the
381             L<Data::MultiValued::AttributeAccessors> method meta class.
382              
383             =head2 C<install_accessors>
384              
385             After the regular L<Moose::Meta::Attribute> method, installs the
386             multi-value accessors.
387              
388             Each installed normal accessor gets a multi-value version
389              
390             You can add or rename the multi-value version by using the attributes
391             described above
392              
393             If you are passing explicit subrefs for your accessors, things won't work.
394              
395             =head2 C<load_multi_value>
396              
397             Retrieves a value from the multi-value object, and stores it in the
398             regular slot in the instance. If the value is not found, clears the
399             slot.
400              
401             This traps the
402             L<Data::MultiValued::Exceptions::NotFound|Data::MultiValued::Exceptions/Data::MultiValued::Exceptions::NotFound>
403             exception that may be thrown by the multi-value object, but re-throws
404             any other exception.
405              
406             =head2 C<raw_clear_value>
407              
408             Clears the instance slot. Does the same as
409             L<Moose::Meta::Attribute/clear_value>, but we need this method because
410             the other one gets changed by this trait.
411              
412             =head2 C<store_multi_value>
413              
414             Gets the value from the regular slot in the instance, and stores it
415             into the multi-value object.
416              
417             =head2 C<get_value>
418              
419             Before the normal method, calls L</load_multi_value>. Normally, no
420             options will be passed to the multi-value object C<get> method.
421              
422             =head2 C<get_multi_value>
423              
424             Sets the options that L</load_multi_value> will use, then calls L</get_value>.
425              
426             The options are passed via an ugly C<local>ised package
427             variable. There might be a better way.
428              
429             =head2 C<set_initial_value>
430              
431             After the normal method, calls L</store_multi_value>.
432              
433             =head2 C<set_value>
434              
435             =head2 C<set_multi_value>
436              
437             Just like L</get_value> and L</get_multi_value>, but calling
438             L</store_multi_value> after the regular C<set_value>
439              
440             =head2 C<has_value>
441              
442             =head2 C<has_multi_value>
443              
444             Just like L</get_value> and L</get_multi_value>.
445              
446             =head2 C<clear_value>
447              
448             =head2 C<clear_multi_value>
449              
450             Call the C<clear> method on the multi-value object.
451              
452             =head2 C<get_multi_read_method>
453              
454             =head2 C<get_multi_write_method>
455              
456             Return the name of the reader or writer method, honoring
457             L</multi_reader>, L</multi_writer> and L</multi_accessor>.
458              
459             =head1 REQUIREMENTS
460              
461             These methods must be provided by any class consuming this role. See
462             L<Data::MultiValued::AttributeTrait::Tags> etc. for examples.
463              
464             =head2 C<multivalue_storage_class>
465              
466             The class to use to create the multi-value objects.
467              
468             =head2 C<opts_to_pass_set>
469              
470             Which options to pass from the multi-value accessors to the C<set>
471             method of the multi-value object.
472              
473             =head2 C<opts_to_pass_get>
474              
475             Which options to pass from the multi-value accessors to the C<get>
476             method of the multi-value object.
477              
478             =head1 Serialisation helpers
479              
480             These are used through
481             L<Data::MultiValued::UglySerializationHelperRole>.
482              
483             =head2 C<_rebless_slot>
484              
485             Blesses the value inside the L</full_storage_slot> of the instance
486             into L</multivalue_storage_class>, then calls C<_rebless_storage> on
487             it.
488              
489             =head2 C<_as_hash>
490              
491             Returns the result of calling C<_as_hash> on the value inside the
492             L</full_storage_slot> of the instance. Returns nothing if the slot
493             does not have a value.
494              
495             =head1 AUTHOR
496              
497             Gianni Ceccarelli <dakkar@thenautilus.net>
498              
499             =head1 COPYRIGHT AND LICENSE
500              
501             This software is copyright (c) 2011 by Net-a-Porter.com.
502              
503             This is free software; you can redistribute it and/or modify it under
504             the same terms as the Perl 5 programming language system itself.
505              
506             =cut
507