Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Confusion with Moo's builder attribute and a file handle

by mgatto (Novice)
on Jul 22, 2013 at 22:51 UTC ( [id://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.

Replies are listed 'Best First'.
Re: Confusion with Moo's builder attribute and a file handle
by tobyink (Canon) 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
Domain Nodelet?
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?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (5)
As of 2024-04-19 20:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found