File Coverage

blib/lib/MooseX/Traits/SetScalarByRef.pm
Criterion Covered Total %
statement 26 28 92.8
branch 3 4 75.0
condition n/a
subroutine 8 8 100.0
pod n/a
total 37 40 92.5


line stmt bran cond sub pod time code
1             package MooseX::Traits::SetScalarByRef;
2              
3 2     2   156240 use 5.010;
  2         18  
4 2     2   12 use strict;
  2         5  
  2         76  
5 2     2   66 use warnings;
  2         7  
  2         85  
6 2     2   1126 use Moose::Role;
  2         1072686  
  2         11  
7 2     2   12341 use Moose::Util::TypeConstraints qw(find_type_constraint);
  2         6  
  2         16  
8              
9             our $VERSION = '0.03';
10              
11             # Supply a default for "is"
12             around _process_is_option => sub {
13             my $next = shift;
14             my $self = shift;
15             my ($name, $options) = @_;
16              
17             if (not exists $options->{is}) {
18             $options->{is} = "rw";
19             }
20              
21             $self->$next(@_);
22             };
23              
24             # Supply a default for "isa"
25             my $default_type;
26             around _process_isa_option => sub {
27             my $next = shift;
28             my $self = shift;
29             my ($name, $options) = @_;
30              
31             if (not exists $options->{isa}) {
32             if (not defined $default_type) {
33             $default_type =
34             find_type_constraint('ScalarRef')->create_child_constraint;
35             $default_type->coercion('Moose::Meta::TypeCoercion'->new)
36             ->add_type_coercions('Value', sub { my $r = $_; \$r });
37             }
38             $options->{isa} = $default_type;
39             }
40              
41             $self->$next(@_);
42             };
43              
44             # Automatically coerce
45             around _process_coerce_option => sub {
46             my $next = shift;
47             my $self = shift;
48             my ($name, $options) = @_;
49              
50             if ( defined $options->{type_constraint}
51             and $options->{type_constraint}->has_coercion
52             and not exists $options->{coerce}) {
53             $options->{coerce} = 1;
54             }
55              
56             $self->$next(@_);
57             };
58              
59             # This allows handles => 1
60             around _canonicalize_handles => sub {
61             my $next = shift;
62             my $self = shift;
63              
64             my $handles = $self->handles;
65             if (!ref($handles) and $handles eq '1') {
66             return ($self->init_arg, 'set_by_ref');
67             }
68              
69             $self->$next(@_);
70             };
71              
72             # Actually install the wrapper
73             around install_delegation => sub {
74             my $next = shift;
75             my $self = shift;
76              
77             my %handles = $self->_canonicalize_handles;
78             for my $key (sort keys %handles) {
79             $handles{$key} eq 'set_by_ref' or next;
80             delete $handles{$key};
81             $self->associated_class->add_method($key,
82             $self->_make_set_by_ref($key));
83             }
84              
85             # When we call $next, we're going to temporarily
86             # replace $self->handles, so that $next cannot see
87             # the set_by_ref bits which were there.
88             my $orig = $self->handles;
89             $self->_set_handles(\%handles);
90             $self->$next(@_);
91             $self->_set_handles($orig); # and restore!
92             };
93              
94             # This generates the coderef for the method that we're
95             # going to install
96             sub _make_set_by_ref {
97 1     1   3 my $self = shift;
98 1         3 my ($method_name) = @_;
99              
100 1         8 my $reader = $self->get_read_method;
101 1         50 my $type = $self->type_constraint;
102 1         49 my $coerce = $self->should_coerce;
103              
104             return sub {
105 3     3   1121 my $obj = shift;
        3      
106 3 100       8 if (@_) {
107             my $new_ref =
108             $coerce
109             ? $type->assert_coerce(@_)
110 1 50       8 : do { $type->assert_valid(@_); $_[0] };
  0         0  
  0         0  
111 1         265 ${ $obj->$reader } = $$new_ref;
  1         21  
112             }
113 3         65 $obj->$reader;
114 1         20 };
115             }
116              
117             1; # /MooseX::Traits::SetScalarByRef
118              
119             __END__
120              
121             =head1 NAME
122              
123             MooseX::Traits::SetScalarByRef - Wrap a ScalarRef attribute's accessors to re-use a reference
124              
125             =head1 SYNOPSIS
126              
127             package Local::Example;
128             use Moose;
129             use Moose::Util::TypeConstraints;
130             use MooseX::Traits::SetScalarByRef;
131            
132             subtype 'TkRef', as 'ScalarRef';
133             coerce 'TkRef', from 'Str', via { my $r = $_; return \$r };
134            
135             has _some_val => (
136             traits => [ 'MooseX::Traits::SetScalarByRef' ],
137             isa => 'TkRef',
138             init_arg => 'some_val',
139             default => 'default value',
140             handles => 1,
141             );
142            
143             package main;
144            
145             my $eg = Local::Example->new;
146             my $ref_addr = refaddr($eg->some_val);
147             $eg->some_val("new string");
148             my $refaddr_after_change = refaddr($eg->some_val);
149             if($ref_addr eq $refaddr_after_change) {
150             print "refaddr did not change";
151             }
152              
153             =head1 DESCRIPTION
154              
155             This module wraps a ScalarRef attribute's accessors to ensure that when the setter is called with a new ScalarRef
156             (or something that can be coerced into a ScalarRef),
157             rather that the usual set action happening,
158             you copy the string stored in the new scalar into the old scalar.
159              
160             =head2 EXPORT
161              
162             None by default.
163              
164              
165             =head1 SEE ALSO
166              
167             L<Moose>, L<Moose::Util::TypeConstraints>
168              
169             =head1 AUTHORS
170              
171             Alex Becker, E<lt>asb@cpan.orgE<gt>
172              
173             =head1 CONTRIBUTORS
174              
175             Many thanks to tobyink. He basicaklly wrote all the code for this module and provided it on L<stackoverflow.com|http://stackoverflow.com/questions/23445500/automatically-generate-moose-attribute-wrapper-methods>.
176              
177             Thanks to rsrchboy and @ether for the valuable feedback in #moose on L<irc.perl.org|http://www.irc.perl.org/>.
178              
179             Thanks to Matt S Trout for the motivation of creating this module: I<Sufficiently encapsulated ugly is indistinguable from beautiful>.
180              
181             =head1 COPYRIGHT AND LICENSE
182              
183             Copyright (C) 2014-2021 by Alex Becker
184              
185             This library is free software; you can redistribute it and/or modify
186             it under the same terms as Perl itself, either Perl version 5.16.3 or,
187             at your option, any later version of Perl 5 you may have available.
188              
189              
190             =cut