File Coverage

blib/lib/MooX/Tag/TO_HASH.pm
Criterion Covered Total %
statement 47 47 100.0
branch 11 12 91.6
condition 10 14 71.4
subroutine 10 10 100.0
pod 1 1 100.0
total 79 84 94.0


line stmt bran cond sub pod time code
1             package MooX::Tag::TO_HASH;
2              
3             # ABSTRACT: Controlled translation of Moo objects into Hashes
4              
5 10     10   2039556 use v5.10;
  10         92  
6              
7 10     10   72 use strict;
  10         22  
  10         235  
8 10     10   54 use warnings;
  10         18  
  10         509  
9              
10             our $VERSION = '0.05';
11              
12 10     10   4573 use Safe::Isa;
  10         4996  
  10         1445  
13 10     10   4845 use Ref::Util qw( is_ref is_blessed_ref is_arrayref is_hashref );
  10         16575  
  10         827  
14 10     10   4646 use MooX::Tag::TO_HASH::Util ':all';
  10         26  
  10         4701  
15              
16             sub _process_value {
17              
18             return \$_[0] unless is_ref( $_[0] );
19              
20             my $ref = $_[0];
21             my $value = $ref;
22              
23             if ( is_blessed_ref( $ref ) ) {
24             # turtles all the way down...
25             my $mth = $ref->$_can( UC_TO_HASH );
26             $value = $ref->$mth if defined $mth;
27             }
28             elsif ( is_arrayref( $ref ) ) {
29             my %new;
30             for my $idx ( 0 .. @{$ref} - 1 ) {
31             my $_ref = _process_value( $ref->[$idx] );
32             $new{$idx} = $$_ref if defined $ref;
33             }
34             if ( keys %new ) {
35             my @replace = @$ref;
36             $replace[$_] = delete $new{$_} for keys %new;
37             $value = \@replace;
38             }
39             }
40             elsif ( is_hashref( $ref ) ) {
41             my %new;
42             for my $key ( keys %$ref ) {
43             my $_ref = _process_value( $ref->{$key} );
44             $new{$key} = $$_ref if defined $ref;
45             }
46             if ( keys %new ) {
47             my %replace = %$ref;
48             $replace{$_} = delete $new{$_} for keys %new;
49             $value = \%replace;
50             }
51             }
52              
53             return \$value;
54             }
55              
56 10     10   82 use Moo::Role;
  10         20  
  10         64  
57             use MooX::TaggedAttributes -propagate,
58             -tags => LC_TO_HASH,
59 10     10   13580 -handler => sub { make_tag_handler( LC_TO_HASH ) };
  10         171146  
  10         108  
  28         245561  
60              
61 10     10   181711 use namespace::clean -except => [ '_tags', '_tag_list' ];
  10         33  
  10         72  
62              
63              
64              
65              
66              
67              
68              
69              
70              
71             sub TO_HASH {
72 24     24 1 112907 my $self = shift;
73              
74 24   50     111 my $to_hash = $self->_tags->tag_attr_hash->{ +LC_TO_HASH } // {};
75              
76             # the structure of %to_hash is complicated because has() may take
77             # multiple attributes. For example,
78              
79             # has ['foo','bar'] => ( is => 'ro', to_hash => '1' );
80              
81             # results in %to_hash looking like this:
82              
83             # bar => {
84             # bar => { omit_if_empty => 0, predicate => "has_bar" },
85             # foo => { omit_if_empty => 0, predicate => "has_foo" },
86             # },
87             # foo => {
88             # bar => { omit_if_empty => 0, predicate => "has_bar" },
89             # foo => { omit_if_empty => 0, predicate => "has_foo" },
90             # },
91              
92 24         50151 my %hash;
93 24         62 for my $attr ( keys %{$to_hash} ) {
  24         86  
94 110         196 my $opt = $to_hash->{$attr}{$attr};
95             # hashes returned by the _tags method are readonly, so need to
96             # check if key exists before querying it to avoid an exception
97             next
98             if exists $opt->{ +IF_EXISTS }
99 110 100 100     275 && !$self->${ \$opt->{ +PREDICATE } };
  48         313  
100              
101             next
102             if exists $opt->{ +IF_DEFINED }
103 86 100 100     240 && !defined $self->${ \$attr };
  14         71  
104              
105             my $name
106             = exists $opt->{ +ALT_NAME }
107 73 100 33     233 ? $opt->{ +ALT_NAME } // $attr
108             : $attr;
109              
110 73         253 my $value = $self->$attr;
111              
112             # return 'as is' if we're not supposed to recurse, or $value
113             # is a normal scalar
114 73 100       153 if ( exists $opt->{ +NO_RECURSE } ) {
115             # do nothing
116             }
117             # possibly turtles all the way down...
118             else {
119 72         152 my $ref_to_value = _process_value( $value );
120 72 50       156 $value = ${$ref_to_value} if defined $ref_to_value;
  72         114  
121             }
122              
123 73         203 $hash{$name} = $value;
124             }
125              
126 24 100 66     330 if ( defined( my $mth = $self->can( '_modify_hashr' ) // $self->can( 'modify_hashr' ) ) ) {
127 3         40 $self->$mth( \%hash );
128             }
129              
130 24         274 return \%hash;
131             }
132              
133              
134              
135              
136             1;
137              
138             #
139             # This file is part of MooX-Tag-TO_HASH
140             #
141             # This software is Copyright (c) 2022 by Smithsonian Astrophysical Observatory.
142             #
143             # This is free software, licensed under:
144             #
145             # The GNU General Public License, Version 3, June 2007
146             #
147              
148             __END__
149              
150             =pod
151              
152             =for :stopwords Diab Jerius Smithsonian Astrophysical Observatory
153              
154             =head1 NAME
155              
156             MooX::Tag::TO_HASH - Controlled translation of Moo objects into Hashes
157              
158             =head1 VERSION
159              
160             version 0.05
161              
162             =head1 SYNOPSIS
163              
164             package My::Farm;
165            
166             use Moo;
167             with 'MooX::Tag::TO_HASH';
168            
169             has cow => ( is => 'ro', to_hash => 1 );
170             has duck => ( is => 'ro', to_hash => 'goose,if_exists', );
171             has horse => ( is => 'ro', to_hash => ',if_defined', );
172             has hen => ( is => 'ro', to_hash => 1, );
173             has secret_admirer => ( is => 'ro', );
174            
175             # and somewhere else...
176            
177             use Data::Dumper;
178             my $farm = My::Farm->new(
179             cow => 'Daisy',
180             duck => 'Frank',
181             secret_admirer => 'Fluffy',
182             );
183            
184             print Dumper $farm->TO_HASH;
185              
186             # resulting in
187              
188             $VAR1 = {
189             'goose' => 'Frank',
190             'cow' => 'Daisy',
191             'hen' => undef
192             };
193              
194             =head1 DESCRIPTION
195              
196             C<MooX::Tag::TO_HASH> is a L<Moo::Role> which provides a controlled method of converting your
197             L<Moo> based object into a hash.
198              
199             Simply mark each field that should be output with the special option
200             C<to_hash> when declaring it:
201              
202             has field => ( is => 'ro', to_hash => 1 );
203              
204             and call the L</TO_HASH> method on your instantiated object.
205              
206             my %hash = $obj->TO_HASH;
207              
208             Fields inherited from superclasses or consumed from roles which use
209             C<MooX::Tag::TO_HASH> are automatically handled.
210              
211             If a field's value is another object, L</TO_HASH> will automatically
212             turn that into a hash if it has its own C<TO_HASH> method (you can
213             also prevent that).
214              
215             =head2 Modifying the generated hash
216              
217             [Originally, this module recommended using a method modifier to the
218             L<TO_HASH> method, this is no longer recommended. See discussion
219             under L<DEPRECATED BEHAVIOR> below.].
220              
221             If the class provides a C<_modify_hashr> method (or for backwards
222             compatibility, C<modify_hashr>), it will be called as
223              
224             $self->_modify_hashr( \%hash );
225              
226             and should modify the passed hash in place.
227              
228             =head2 Usage
229              
230             Add the C<to_hash> option to each field which should be
231             included in the hash. C<to_hash> can either take a value of C<1>,
232             e.g.
233              
234             has field => ( is => 'ro', to_hash => 1 );
235              
236             or a string which looks like one of these:
237              
238             alternate_name
239             alternate_name,option_flag,option_flag,...
240             ,option_flag,option_flag,...
241              
242             If C<alternate_name> is specified, that'll be the key used in the
243             output hash.
244              
245             C<option_flag> may be one of the following:
246              
247             =over
248              
249             =item C<if_exists>
250              
251             Only output the field if it was set. This uses L</Moo>'s attribute
252             predicate (one will be added to the field if it not already
253             specified).
254              
255             It I<will> be output if the field is set to C<undef>.
256              
257             A synonym for this is C<omit_if_empty>, for compatibility with
258             L<MooX::TO_JSON>.
259              
260             =item C<if_defined>
261              
262             Only output the field if it was set and its value is defined.
263              
264             =item C<no_recurse>
265              
266             If a field is an object, don't try and turn it into a hash via its
267             C<TO_HASH> method.
268              
269             (Yes, this name is backwards, but eventually a separate C<recurse>
270             option may become available which limits the recursion depth).
271              
272             =back
273              
274             =head1 METHODS
275              
276             =head2 TO_HASH
277              
278             %hash = $obj->TO_HASH
279              
280             This method is added to the consuming class or role.
281              
282             =head1 EXAMPLES
283              
284             =head2 Modifying the generated hash
285              
286             package My::Test::C4;
287            
288             use Moo;
289             with 'MooX::Tag::TO_HASH';
290            
291             has cow => ( is => 'ro', to_hash => 1 );
292             has duck => ( is => 'ro', to_hash => 'goose,if_exists', );
293             has horse => ( is => 'ro', to_hash => ',if_defined', );
294             has hen => ( is => 'ro', to_hash => 1, );
295             has secret_admirer => ( is => 'ro', );
296            
297             # upper case the hash keys
298             sub modify_hashr {
299             my ( $self, $hashr ) = @_;
300             $hashr->{ uc $_ } = delete $hashr->{$_} for keys %$hashr;
301             };
302            
303             # and elsewhere:
304             use Data::Dumper;
305            
306             print Dumper(
307             My::Test::C4->new(
308             cow => 'Daisy',
309             hen => 'Ruby',
310             duck => 'Donald',
311             horse => 'Ed',
312             secret_admirer => 'Nemo'
313             )->TO_HASH
314             );
315              
316             # resulting in
317              
318             $VAR1 = {
319             'HEN' => 'Ruby',
320             'GOOSE' => 'Donald',
321             'HORSE' => 'Ed',
322             'COW' => 'Daisy'
323             };
324              
325             =head1 DEPRECATED BEHAVIOR
326              
327             =head2 Using method modifiers to modify the results
328              
329             Previously it was suggested that the C<around> method modifier be used
330             to modify the resultant hash. However, if both a child and parent
331             class consume the C<MooX::Tag::TO_HASH> role and the parent has
332             modified C<TO_HASH>, the parent's modified C<TO_HASH> will not be run;
333             instead the original C<TO_HASH> will. For example
334              
335             package Role {
336             use Moo::Role;
337             sub foo { print "Role\n" }
338             }
339            
340             package Parent {
341             use Moo;
342             with 'Role';
343             before 'foo' => sub { print "Parent\n" };
344             }
345            
346             package Child {
347             use Moo;
348             extends 'Parent';
349             with 'Role';
350             before 'foo' => sub { print "Child\n" };
351             }
352            
353             Child->new->foo;
354              
355             results in
356              
357             Child
358             Role
359              
360             Note it does not output C<Parent>.
361              
362             =head1 SUPPORT
363              
364             =head2 Bugs
365              
366             Please report any bugs or feature requests to bug-moox-tag-to_hash@rt.cpan.org or through the web interface at: L<https://rt.cpan.org/Public/Dist/Display.html?Name=MooX-Tag-TO_HASH>
367              
368             =head2 Source
369              
370             Source is available at
371              
372             https://gitlab.com/djerius/moox-tag-to_hash
373              
374             and may be cloned from
375              
376             https://gitlab.com/djerius/moox-tag-to_hash.git
377              
378             =head1 SEE ALSO
379              
380             Please see those modules/websites for more information related to this module.
381              
382             =over 4
383              
384             =item *
385              
386             L<MooX::Tag::TO_JSON - the sibling class to this one.|MooX::Tag::TO_JSON - the sibling class to this one.>
387              
388             =item *
389              
390             L<MooX::TO_JSON - this is similar, but doesn't handle fields inherited from super classes or consumed from roles.|MooX::TO_JSON - this is similar, but doesn't handle fields inherited from super classes or consumed from roles.>
391              
392             =back
393              
394             =head1 AUTHOR
395              
396             Diab Jerius <djerius@cpan.org>
397              
398             =head1 COPYRIGHT AND LICENSE
399              
400             This software is Copyright (c) 2022 by Smithsonian Astrophysical Observatory.
401              
402             This is free software, licensed under:
403              
404             The GNU General Public License, Version 3, June 2007
405              
406             =cut