File Coverage

blib/lib/MooseX/Attribute/Localize.pm
Criterion Covered Total %
statement 38 42 90.4
branch 13 20 65.0
condition n/a
subroutine 9 9 100.0
pod 0 1 0.0
total 60 72 83.3


line stmt bran cond sub pod time code
1             package MooseX::Attribute::Localize;
2             our $AUTHORITY = 'cpan:YANICK';
3             # ABSTRACT: localize attribute values within a scope
4             $MooseX::Attribute::Localize::VERSION = '0.1.2';
5 2     2   1030982 use Moose::Role;
  2         9165  
  2         9  
6              
7             has _value_stack => (
8             traits => [ qw/ Array / ],
9             is => 'ro',
10             default => sub { [] },
11             handles => {
12             _push_value => 'push',
13             _pop_value => 'pop',
14             },
15             );
16              
17             has localize_push => ( is => 'ro', predicate => 'has_localize_push' );
18             has localize_pop => ( is => 'ro', predicate => 'has_localize_pop' );
19              
20             before '_canonicalize_handles' => sub {
21             my( $self ) = @_;
22              
23             my $handles = $self->handles;
24              
25             $_ = sub {
26 3     3   3749 my $object = shift;
  2     2   5  
        2      
27              
28 3         15 my $attr = $object->meta->get_attribute( $self->name );
  2         29  
29              
30 3         107 my ( $old ) = $attr->_push_value( $attr->get_value($object) );
  2         58  
31            
32 3         15 $attr->clear_value($object);
  2         19  
33              
34 3         71 my $new_value;
  2         55  
35              
36 3 100       21 $attr->set_value( $object, $new_value = shift ) if @_;
  2 50       19  
37              
38 3 50       410 warn "localize called in void context is a no-op\n"
  2 50       401  
39             unless defined wantarray;
40              
41 3 50       131 if( my $method = $attr->localize_push ) {
  2 50       103  
42             my $func = ref $method ? $method :
43 0 0   1   0 sub { my $self = shift; $self->$method(@_) };
  0 100       0  
  0         0  
  2         11  
  1         2  
  1         6  
44 0         0 $func->( $object, $new_value, $old, $attr );
  2         6  
45             }
46              
47 3         17 return MooseX::Attribute::Localize::Sentinel->new(
  2         31  
48             attribute => $attr,
49             object => $object,
50             );
51             } for grep { $_ eq 'localize' } values %$handles;
52              
53             $_ = sub {
54             my $object = shift;
55              
56             my $attr = $object->meta->get_attribute( $self->name );
57              
58             my @values = reverse @{ $attr->_value_stack };
59             unshift @values, $attr->get_value($object) if $attr->has_value($object);
60              
61             return @values;
62              
63             } for grep { $_ eq 'localize_stack' } values %$handles;
64              
65             };
66              
67             {
68             package MooseX::Attribute::Localize::Sentinel;
69             our $AUTHORITY = 'cpan:YANICK';
70             $MooseX::Attribute::Localize::Sentinel::VERSION = '0.1.2';
71 2     2   11491 use Moose;
  2         3  
  2         11  
72              
73             has [qw/ attribute object /] => ( is => 'ro' );
74              
75             sub DEMOLISH {
76 17     17 0 17407 my $self = shift;
77 17         275 my $old_value = $self->attribute->get_value( $self->object );
78 17         1098 my $new_value = $self->attribute->_pop_value;
79 17         821 $self->attribute->set_value( $self->object, $new_value );
80 17 100       1197 if( my $method = $self->attribute->localize_pop ) {
81             my $func = ref $method ? $method : sub {
82 1     13   2 my $self = shift;
83 1         6 $self->$method(@_);
84 14 100       1635 };
85 2         88 $func->($self->object,$new_value,$old_value,$self->attribute);
86             }
87             }
88             }
89              
90             {
91             package Moose::Meta::Attribute::Custom::Trait::Localize;
92             our $AUTHORITY = 'cpan:YANICK';
93             $Moose::Meta::Attribute::Custom::Trait::Localize::VERSION = '0.1.2';
94 2     2   1382 sub register_implementation { 'MooseX::Attribute::Localize' }
95              
96             }
97              
98             1;
99              
100             __END__
101              
102             =pod
103              
104             =encoding UTF-8
105              
106             =head1 NAME
107              
108             MooseX::Attribute::Localize - localize attribute values within a scope
109              
110             =head1 VERSION
111              
112             version 0.1.2
113              
114             =head1 SYNOPSIS
115              
116             package Foo;
117              
118             use Moose;
119             use MooseX::Attribute::Localize;
120              
121             has 'bar' => (
122             traits => [ 'Localize' ],
123             is => 'rw',
124             handles => {
125             set_local_bar => 'localize'
126             },
127             );
128              
129             my $foo = Foo->new( bar => 'a' );
130              
131             print $foo->bar; # 'a'
132              
133             {
134             my $sentinel = $foo->set_local_bar( 'b' );
135             print $foo->bar; # 'b'
136              
137             $foo->bar('c');
138             print $foo->bar; # 'c'
139             }
140              
141             print $foo->bar; # 'a'
142              
143             =head1 DESCRIPTION
144              
145             Attributes that are given the trait C<Localize> can
146             handle a C<localize> delegation, which stashes away
147             the current value of the attribute and replaces it
148             with a local value, mimicking the behavior of
149             Perl's own C<local>.
150              
151             The delegated method returns a sentinel variable.
152             Once this variable gets out of scope, the attribute
153             returns to its previous value.
154              
155             If the delegated method
156             is called in a void context, a warning will be issued as
157             the sentinel will immediately get out of scope, which
158             turns the whole thing into a glorious no-op.
159              
160             =head1 PROVIDED DELEGATION METHODS
161              
162             =head2 localize( $new_value )
163              
164             Localizes the attribute. If a C<$new_value> is provided, initializes the newly localized
165             value to it.
166              
167             The method returns a sentinel object that will return the attribute to its previous value once it gets
168             out of scope. The method will warn if it is called in a void context (as the sentinel will immediately
169             falls out of scope).
170              
171             =head2 localize_stack
172              
173             Returns the stack of values for the attribute, including the current value.
174              
175             {
176             package Foo;
177              
178             use Moose;
179             use MooseX::Attribute::Localize;
180              
181             has bar => (
182             traits => [ 'Localize' ],
183             is => 'rw',
184             handles => {
185             local_bar => 'localize',
186             bar_stack => 'localize_stack',
187             },
188             );
189             }
190              
191             my $foo = Foo->new( bar => 'a' );
192            
193             {
194             my $s = $foo->local_bar('b');
195             my @stack = $self->bar_stack; # ( 'a', 'b' )
196             }
197              
198             =head1 ATTRIBUTE ARGUMENTS
199              
200             has bar => (
201             traits => [ 'Localize' ],
202             is => 'rw',
203             localize_push => 'spy_on_push',
204             localize_pop => sub {
205             my( $object, $new, $old, $attribute ) = @_;
206             ...;
207             },
208             handles => {
209             local_bar => 'localize',
210             bar_stack => 'localize_stack',
211             },
212             );
213              
214             sub spy_on_push {
215             my( $self, $new, $old, $attribute ) = @_;
216             ...;
217             }
218              
219             =head2 localize_push
220              
221             If defined, will be called when a new value is pushed unto the attribute's
222             stack. Can be the name of a method of the parent object, or a coderef.
223              
224             When called,
225             the associated function/method will be passed the object, the new pushed
226             value, the previous one, and the attribute object.
227              
228             =head2 localize_pop
229              
230             If defined, will be called when a new value is popped from the attribute's
231             stack. Can be the name of a method of the parent object, or a coderef.
232              
233             When called,
234             the associated function/method will be passed the object, the new popped
235             value, the previous one, and the attribute object.
236              
237             =head1 AUTHOR
238              
239             Yanick Champoux <yanick@cpan.org>
240              
241             =head1 COPYRIGHT AND LICENSE
242              
243             This software is copyright (c) 2015 by Yanick Champoux.
244              
245             This is free software; you can redistribute it and/or modify it under
246             the same terms as the Perl 5 programming language system itself.
247              
248             =cut