File Coverage

blib/lib/MooX/ObjectBuilder.pm
Criterion Covered Total %
statement 57 59 96.6
branch 17 20 85.0
condition 5 6 83.3
subroutine 23 23 100.0
pod n/a
total 102 108 94.4


line stmt bran cond sub pod time code
1 9     12   491009 use 5.008;
  9         35  
  9         378  
2 9     9   52 use strict;
  9         16  
  9         271  
3 9     9   58 use warnings;
  9         16  
  9         471  
4              
5 9 50   9   598 BEGIN { if ($] < 5.010000) { require UNIVERSAL::DOES } };
  0         0  
6              
7             package MooX::ObjectBuilder;
8              
9             our $AUTHORITY = 'cpan:TOBYINK';
10             our $VERSION = '0.002';
11              
12 9     9   8580 use B::Hooks::EndOfScope;
  9         86651  
  9         64  
13 9     9   9466 use Exporter::Shiny our(@EXPORT) = qw(make_builder);
  9         40381  
  9         80  
14 9     9   9378 use Lexical::Accessor;
  9         112257  
  9         139  
15 9     9   26923 use MooseX::ConstructInstance -with;
  9         119455  
  9         132  
16 9     9   5633 use Sub::Name qw(subname);
  9         38  
  9         1250  
17              
18             sub _generate_make_builder
19             {
20 11     11   3991 my $me = shift;
21 11         28 my ($name, $args, $globals) = @_;
22            
23 11         58 lexical_has(accessor => \(my $storage));
24 11         6791 my @need_to_store;
25            
26 11         30 my $caller = $globals->{into};
27             # around BUILD
28             on_scope_end {
29 9     9   55 no strict 'refs';
  9         16  
  9         4571  
30 11 50   11   1999 my $next = exists(&{"$caller\::BUILD"}) ? \&{"$caller\::BUILD"} : undef;
  11         74  
  0         0  
31 11         63 *{"$caller\::BUILD"} = sub {
32 15     15   51198 my $self = shift;
        5      
33 15         35 my ($params) = @_;
34 15 100       222 $self->$storage({ map exists($params->{$_})?($_=>$params->{$_}):(), @need_to_store });
35 15 50       544 $self->$next(@_) if $next;
36 11         60 };
37 11         31 subname("$caller\::BUILD", \&{"$caller\::BUILD"});
  11         117  
38 11         95 };
39            
40             return sub { # make_builder
41 20     20   245352 my $klass = shift;
42 11         67 my %attrs =
43 2         14 @_==1 && ref($_[0]) eq 'HASH' ? %{$_[0]} :
44 20 100 100     283 @_==1 && ref($_[0]) eq 'ARRAY' ? map(+($_=>$_), @{$_[0]}) :
    100 66        
45             @_;
46 20         68 push @need_to_store, keys(%attrs);
47             my $code = sub {
48 28     28   57243 my $self = shift;
        5      
        5      
        5      
        5      
        23      
        23      
        2      
49 28         112 my $storage = $self->$storage;
50 28 100       477 my %args = map exists($storage->{$_})?($attrs{$_}=>$storage->{$_}):(), keys(%attrs);
51 28 100       286 my $bless = exists($args{'__CLASS__'}) ? delete($args{'__CLASS__'}) : $klass;
52            
53 28 100       535 $self->DOES('MooseX::ConstructInstance')
54             ? $self->construct_instance($bless, \%args)
55             : MooX::ObjectBuilder->construct_instance($bless, \%args);
56 20         117 };
57 20 100       131 wantarray ? ('lazy', builder => $code) : $code;
58             }
59 11         350 }
60              
61             1;
62              
63             __END__
64              
65             =pod
66              
67             =encoding utf-8
68              
69             =for stopwords Torbjørn
70              
71             =head1 NAME
72              
73             MooX::ObjectBuilder - lazy construction of objects from extra init args
74              
75             =head1 SYNOPSIS
76              
77             package Person {
78             use Moo;
79            
80             has name => (is => "ro");
81             has title => (is => "ro");
82             }
83            
84             package Organization {
85             use Moo;
86             use MooX::ObjectBuilder;
87            
88             has name => (is => "ro");
89             has boss => (
90             is => make_builder(
91             "Person" => (
92             boss_name => "name",
93             boss_title => "title",
94             ),
95             ),
96             );
97             }
98            
99             my $org = Organization->new(
100             name => "Catholic Church",
101             boss_name => "Francis",
102             boss_title => "Pope",
103             );
104            
105             use Data::Dumper;
106             print Dumper( $org->boss );
107              
108             =head1 DESCRIPTION
109              
110             This module exports a function C<make_builder> which can be used to
111             generate lazy builders suitable for L<Moo> attributes. The import
112             procedure also performs some setup operations on the caller class
113             necessary for C<make_builder> to work correctly.
114              
115             =head2 Functions
116              
117             =over
118              
119             =item C<< make_builder( $class|$coderef, \%args|\@args|%args ) >>
120              
121             The C<make_builder> function conceptually takes two arguments, though
122             the second one (which is normally a hashref or arrayref) may be passed
123             as a flattened hash.
124              
125             The C<< %args >> hash is a mapping of argument names where keys are
126             names in the "aggregating" or "container" class (i.e. "Organization"
127             in the L</SYNOPSIS>) and values are names in the "aggregated" or
128             "contained" class (i.e. "Person" in the L</SYNOPSIS>).
129              
130             If C<< \@args >> is provided instead, this is expanded into a hash as
131             follows:
132              
133             my %args = map { $_ => $_ } @args;
134              
135             The builder returned by this function will accept arguments from the
136             aggregating class and map them into arguments for the aggregated class.
137             The builder will then construct an instance of C<< $class >> passing
138             it a hashref of arguments. If C<< $coderef >> has been provided instead
139             of a class name, this will be called with the hashref of arguments
140             instead.
141              
142             The C<make_builder> function behaves differently in scalar and list
143             context. In list context, it returns a three item list. The first two
144             items are the strings C<< "lazy" >> and C<< "builder" >>; the third
145             item is the builder coderef described above. In scalar context, only
146             the coderef is returned. Thus the following two examples work
147             equivalently:
148              
149             # Scalar context
150             my $builder = make_builder($class, {...});
151             has attr => (
152             is => "lazy",
153             builder => $builder,
154             );
155              
156             # List context
157             has attr => (
158             is => make_builder($class, {...}),
159             );
160              
161             =back
162              
163             =head2 Class Setup
164              
165             On import, this module installs a sub called C<BUILD> into your class.
166             If your class already has a sub with this name, it will be wrapped.
167              
168             The point of this sub is to capture argument passed to the aggregating
169             class' constructor, to enable them to be later forwarded to the
170             aggregated class.
171              
172             See also: L<Moo/"BUILD">.
173              
174             =head2 Using MooX::ObjectBuilder with Moose and Mouse
175              
176             It is possible to use C<make_builder> in scalar context with L<Moose>
177             and L<Mouse> classes:
178              
179             has attr => (
180             is => "ro",
181             lazy => 1,
182             default => scalar make_builder($class, {...}),
183             );
184              
185             =head2 MooseX::ConstructInstance
186              
187             If your object does the L<MooseX::ConstructInstance> role, then this
188             module will automatically do the right thing and delegate to that for
189             the actual object construction.
190              
191             =head1 BUGS
192              
193             Please report any bugs to
194             L<http://rt.cpan.org/Dist/Display.html?Queue=MooX-ObjectBuilder>.
195              
196             =head1 SEE ALSO
197              
198             L<Moo>, L<Moose>, L<Mouse>.
199              
200             L<MooseX::ConstructInstance>.
201              
202             L<MooX::LazyRequire>,
203             L<MooseX::LazyRequire>,
204             L<MooseX::LazyCoercion>,
205             etc.
206              
207             =head1 AUTHOR
208              
209             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
210              
211             =head1 CREDITS
212              
213             Most of the test suite was written by Torbjørn Lindahl (cpan:TORBJORN).
214              
215             Various advice was given by Graham Knop (cpan:HAARG) and Matt S Trout
216             (cpan:MSTROUT).
217              
218             =head1 COPYRIGHT AND LICENCE
219              
220             This software is copyright (c) 2014 by Toby Inkster.
221              
222             This is free software; you can redistribute it and/or modify it under
223             the same terms as the Perl 5 programming language system itself.
224              
225             =head1 DISCLAIMER OF WARRANTIES
226              
227             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
228             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
229             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.