Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Confusion with Moo's builder attribute and a file handle

by mgatto (Novice)
on Jul 22, 2013 at 22:51 UTC ( #1045741=perlquestion: print w/ replies, xml ) Need Help??
mgatto has asked for the wisdom of the Perl Monks concerning the following question:

In my moo-based class, building this attribute works fine:

has +dbh => ( is => 'lazy', # must return only a code ref builder => sub { DBI->connect( "dbi:CSV:", undef, undef, { RaiseError => 1, PrintError => 1, f_ext => ".csv/r", # Better performance with XS csv_class => "Text::CSV_XS", # csv_null => 1, } ) or die "Cannot connect: $DBI::errstr"; } );
When dumped, as expected it returns: $VAR1 = bless( {}, 'DBI::db' );

But, this code returns just a string instead of a file handle, which fails when I try to performs ops on it:

has base_file => ( is => 'rw', required => 1, #allow external names to be different from class attribute init_arg => 'base', builder => sub { my $base_fh = IO::File->new( $_[0], '<' ) or die "$_[0]: $!"; $base_fh->binmode(":utf8"); return $base_fh; } );

This is what I get:

$VAR2 = '..\\t\\merge_into.csv';

which of course fails when wanting to pass it to Text::CSV_XS:

... Can't call method "getline" without a package or object reference

Is there some special case for handling file handles in Moo? I tried returning a ref to the filehandle instead, but no joy. It worked fine as a procedural script before I converted it into a class, so the strategy works.

Comment on Confusion with Moo's builder attribute and a file handle
Select or Download Code
Replies are listed 'Best First'.
Re: Confusion with Moo's builder attribute and a file handle
by tobyink (Abbot) on Jul 23, 2013 at 08:40 UTC

    OK; I think you're confused about what builders are supposed to do. Your builder will only be executed if you do NOT provide a value for the base_file attribute.

    use v5.14; use Data::Dumper; package MyClass { use Moo; has base_file => ( is => 'rw', required => 1, init_arg => 'base', builder => sub { return 'bar' }, ); } # builder sub is *NOT* executed my $object1 = MyClass2->new(base => "foo"); print Dumper($object->base_file); # $VAR1 = 'foo' # builder sub *IS* executed my $object2 = MyClass->new(); print Dumper($object2->base_file); # $VAR1 = 'bar'

    Further, you seem to be assuming that within the builder sub, $_[0] is some sort of file name. It's not; it's $self.

    I think what you want is coercions...

    use v5.14; use Data::Dumper; use IO::File; package MyClass { use Moo; has base_file => ( is => 'rw', required => 1, init_arg => 'base', coerce => sub { my $base_fh = IO::File->new( $_[0], '<' ) or die "$_[0]: $ +!"; $base_fh->binmode(":utf8"); return $base_fh; }, ); } my $object = MyClass->new(base => __FILE__); print Dumper($object->base_file);

    In summary: use coercions to munge incoming values; use builders to provide a default value when there is no incoming value.

    Here's another way you could have done it... provide a base attribute which is the filename as a string, and then build the base_file attribute from that:

    use v5.14; use Data::Dumper; use IO::File; package MyClass { use Moo; has base => ( is => 'rw', required => 1, ); has base_file => ( is => 'rw', lazy => 1, builder => sub { my $base_fh = IO::File->new($_[0]->base, '<' ) or die($_[0 +]->base . ": $!"); $base_fh->binmode(":utf8"); return $base_fh; }, ); } my $object = MyClass->new(base => __FILE__); print Dumper($object->base_file);
    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

      Thanks, tobyink. You're right: coerce was the right method to use. 20/20 in retrospect :-)

Re: Confusion with Moo's builder attribute and a file handle (code)
by Anonymous Monk on Jul 23, 2013 at 00:44 UTC

    Me too :)

    #!/usr/bin/perl -- use strict; use warnings; use Data::Dump; use IO::File; Main( @ARGV ); exit( 0 ); BEGIN { package Shack; use Moo; has( qw/ crabs is rw required 1 init_arg base /, builder => sub { warn "## BUILDER { @_ } @{[Data::Dump::pp( @_ )]}\n"; IO::File->new( $_[0] ) or die qq{"$_[0]": $!\n$^E\n }; }, ); $INC{'Shack.pm'} = __FILE__; } sub Main { use Shack; dd( Shack->new( base => __FILE__ ) ); dd( Shack->new( crabs => __FILE__ ) ); } __END__ bless({ crabs => "crabshack.pl" }, "Shack") ## BUILDER { Shack=HASH(0xc3f7ec) } bless({}, "Shack") "Shack=HASH(0xc3f7ec)": No such file or directory The system cannot find the file specified at crabshack.pl line 23.

    So BUILDER doesn't get called if there is a base argument (named in init_arg

    I think the idea is to have

    has qw/ base required 1 /; has qw/ crabs is rw / , builder => sub { IO::File->new( $self->base ) +or ... };

    But I don't get it, even after looking at
    https://metacpan.org/source/HAARG/Moo-1.003000/t/init-arg.t
    https://metacpan.org/source/HAARG/Moo-1.003000/t/lazy_isa.t

    No, I'm not familiar with Moose way of doing OOP, maybe the Moose docs explain the concepts better so the Moo docs make sense, but I'm not that interested

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1045741]
Approved by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (11)
As of 2015-07-07 22:14 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (93 votes), past polls