Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic

Using Moose and BUILDARGS, but having trouble

by roboticus (Chancellor)
on Nov 29, 2018 at 21:36 UTC ( #1226515=perlquestion: print w/replies, xml ) Need Help??

roboticus has asked for the wisdom of the Perl Monks concerning the following question:

Hello, all:

I've decided to try Moose on my current project, and I've hit a stumbling block. I'm reviewing the documentation, but haven't found what I'm looking for, so I thought I'd see if anyone could offer any suggestions.

I'm building an ad-hoc parser for a file, and am hoping to leverage the BUILDARGS method to parse out the chunks at each level. I've stripped it down to a relatively short example, like so:

# use strict; use warnings; package Funky::Vertex; use Moose; use Data::Dump 'pp'; has 'X' => (is=>'rw', isa=>'Num'); has 'Y' => (is=>'rw', isa=>'Num'); around BUILDARGS => sub { my $orig = shift; my $class = shift; print "Vertex(<$_[0]>)\n"; if (@_==1 && !ref $_[0]) { # We were handed a single scalar, so we're expecting a string +like "##.##,##.##" # representing the X and Y coordinates my @list = split /,/, $_[0]; print "\tV: ", pp(\@list), "\n"; return $class->$orig( X=>$list[0], Y=>$list[1] ); } else { return $class->$orig(@_); } }; package Funky::Rectangle; use Moose; use Data::Dump 'pp'; has 'LL' => (is=>'rw', isa=>'Funky::Vertex'); has 'UR' => (is=>'rw', isa=>'Funky::Vertex'); around BUILDARGS => sub { my $orig = shift; my $class = shift; print "Rectangle(<$_[0]>)\n"; if (@_==1 && !ref $_[0]) { # We were handed a single scalar, so we're expecting a string +representing # a pair of vertices, like "##.##,##.##;##.##,##.##" # representing the LL and UR corners my @list = split /;/, $_[0]; print "\tRB: ", pp(\@list), "\n"; return $class->$orig( LL=>$list[0], UR=>$list[1] ); ### NOTE +BELOW ### } else { return $class->$orig(@_); } }; 1;

As you can see, a Funky::Vertex has an X and Y coordinate, but if you construct it with a scalar, it expects a string like "<num>,<num>" and will split that into a hash of { X=><num>, Y=><num> }. Similarly, a Funky::Rectangle has a lower left vertex (LL) and an upper-right vertex (UR). If called with a scalar like "<vertex>;<vertex>", it would split that into a hash of { LL=><vertex>, UR=><vertex> } to build itself.

The problem I'm having is that I was hoping that Moose would recurse through the BUILDARGS as needed to build all the items, but instead it seems to assume that all the arguments are fully built already:

#!env perl use strict; use warnings; use Test::More; use lib "."; BEGIN { use_ok( 'Funky' ); } require_ok( 'Funky' ); subtest 'Vertex' => sub { my $t = Funky::Vertex->new("15,22"); isa_ok($t, 'Funky::Vertex'); is($t->X(), 15, "x val"); is($t->Y, 22, "y val"); done_testing(); }; subtest 'Rectangle' => sub { my $t = Funky::Rectangle->new("10,10;15,25"); isa_ok($t, 'Funky::Rectangle'); is($t->LL->X, 10, "LL x val"); is($t->LL->Y, 10, "LL y val"); is($t->UR->X, 15, "UR x val"); is($t->UR->Y, 25, "UR y val"); done_testing(); };

When I run this example, I'm getting:

$ perl ok 1 - use Funky; ok 2 - require Funky; # Subtest: Vertex Vertex(<15,22>) V: [15, 22] ok 1 - An object of class 'Funky::Vertex' isa 'Funky::Vertex' ok 2 - x val ok 3 - y val 1..3 ok 3 - Vertex # Subtest: Rectangle Rectangle(<10,10;15,25>) RB: ["10,10", "15,25"] 1..0 # No tests run! not ok 4 - No tests run for subtest "Rectangle" # Failed test 'No tests run for subtest "Rectangle"' # at line 30. Attribute (UR) does not pass the type constraint because: Validation f +ailed for 'Funky::Vertex' with value 15,25 (not isa Funky::Vertex) at + /usr/local/lib/perl5/site_perl/5.26/x86_64-cygwin-threads/Moose/Obje line 24 Moose::Object::new('Funky::Rectangle', '10,10;15,25') called a +t line 23 main::__ANON__ at /usr/local/share/perl5/site_perl/5.26/Test/B line 339 eval {...} at /usr/local/share/perl5/site_perl/5.26/Test/Build line 339 Test::Builder::subtest('Test::Builder=HASH(0x60008c060)', 'Rec +tangle', 'CODE(0x6007d0e58)') called at /usr/local/share/perl5/site_p +erl/5.26/Test/ line 807 Test::More::subtest('Rectangle', 'CODE(0x6007d0e58)') called a +t line 30 # Tests were run but no plan was declared and done_testing() was not s +een. # Looks like your test exited with 255 just after 4.

I can make the test pass by changing the line marked "### NOTE BELOW ###" to:

 return $class->$orig( LL=>Funky::Vertex->new($list[0]), UR=>Funky::Vertex->new($list[1]) );

However, I find that to be a bit of a problem because:

  1. As I edit the parser, I find more and more lines that need to be changed, and
  2. The information is already present in the has entries for which constructor to call for each thing.

It feels like there should be a relatively simple way to get the behavior I want without having to repeat the same information (in has and in BUILDARGS). I've not come up with a solution, though.

Anyone have any suggestions?


When your only tool is a hammer, all problems look like your thumb.

Replies are listed 'Best First'.
Re: Using Moose and BUILDARGS, but having trouble
by tobyink (Canon) on Nov 30, 2018 at 00:07 UTC

    Use coercion in your attributes in Funky::Rectangle.

    package Funky::Rectangle; use Moose; use Types::Standard qw(InstanceOf Str); use Data::Dump 'pp'; my $vertex_type = InstanceOf->of('Funky::Vertex')->plus_constructors(S +tr, "new"); has 'LL' => (is=>'rw', isa=>$vertex_type, coerce=>1); has 'UR' => (is=>'rw', isa=>$vertex_type, coerce=>1); # ...

    Update: note that this can be achieved using Moose's built-in type constraints, but adding coercions to those has a global effect which can result in spooky action at a distance.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1226515]
Approved by marto
Front-paged by stevieb
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (3)
As of 2022-01-28 07:54 GMT
Find Nodes?
    Voting Booth?
    In 2022, my preferred method to securely store passwords is:

    Results (73 votes). Check out past polls.