Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical

Moo different parameters for constructor

by williamcharles (Initiate)
on May 16, 2019 at 11:01 UTC ( #11100067=perlquestion: print w/replies, xml ) Need Help??

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

I am creating a custom MyDate class using Moo with attributes year, month and day. I want to have three different ways to instantiate MyDate class.
1. MyDate->new(); 2. MyDate->new(year=>2019, month=>05, day=>16); 3. MyData->new(date=>20190516);

1. When called like this, I would like to use the Date::Calc::Today to assign the values to year, month and day

2. When called like this, the values passed will be set to instance data.

3. When called like this, I would want to regex and find year, month and day and assign it to instance data.

I am not sure on how to achieve using Moo. Can anyone help me with the template. Thanks in advance.

Replies are listed 'Best First'.
Re: Moo different parameters for constructor
by haukex (Canon) on May 16, 2019 at 11:17 UTC

    (Update: As LanX also said in the meantime,) See the documentation for BUILDARGS in Moo:

    use warnings; use strict; package MyDate { use Moo; use Types::Standard qw/ Int /; use Carp; use Date::Calc qw/Today/; use namespace::clean; has year => ( is=>'ro', required=>1, isa=>Int ); has month => ( is=>'ro', required=>1, isa=>Int ); has day => ( is=>'ro', required=>1, isa=>Int ); around BUILDARGS => sub { my ( $orig, $class, %args ) = @_; my %newargs; # NOTE: This does not handle incorrect arguments! if ( keys %args ) { if ( keys %args == 3 ) { %newargs = %args; } elsif ( $args{date} ) { @newargs{qw/year month day/} = $args{date}=~/^(\d{4})(\d\d)(\d\d)$/ or croak "bad date $args{date}"; } } else { my ($year,$month,$day) = Today(); %newargs = (year=>$year, month=>$month, day=>$day); } return $class->$orig(%newargs); }; sub ymd { my $self = shift; return $self->year.'-'.$self->month.'-'.$self->day; } } print "1. ", MyDate->new()->ymd, "\n"; print "2. ", MyDate->new(year=>2019, month=>5, day=>14)->ymd, "\n"; print "3. ", MyDate->new(date=>20190512)->ymd, "\n"; __END__ 1. 2019-5-16 2. 2019-5-14 3. 2019-05-12

    Update: To be clear, this is just a template (as requested), since it doesn't really do any checking of the constructor args. Personally, I also like to add use MooX::StrictConstructor; after use namespace::clean; to prevent typos.

      Thanks for the tip. Also, How can we validate constructor args?
        Also, How can we validate constructor args?

        There's several different ways to do this. The code I showed already uses Type-Tiny, see its documentation on how to use it for tighter constraints, and see the documentation for isa in Moo. I also mentioned MooX::StrictConstructor above.

Re: Moo different parameters for constructor
by choroba (Bishop) on May 16, 2019 at 12:52 UTC
    In the spirit of TIMTOWTDI, here's a way without BUILDARGS. All the attributes are lazy and depend on each other. I used Time::Piece instead of Date::Calc::Today, but that's not relevant to the question.
    #! /usr/bin/perl { package MyDate; use Moo; use Time::Piece; use namespace::clean; has year => (is => 'lazy'); has month => (is => 'lazy'); has day => (is => 'lazy'); has date => (is => 'lazy', predicate => 'has_date'); sub _from_date { my ($self, $what, $pos, $length) = @_; return substr $self->date, $pos, $length if $self->has_date; return localtime->$what } sub _build_year { $_[0]->_from_date(year => 0, 4) } sub _build_month { $_[0]->_from_date(mon => 4, 2) } sub _build_day { $_[0]->_from_date(mday => 6, 2) } my %format = ( year => '%04d' ); sub _build_date { my ($self) = @_; join "", map sprintf($format{$_} || '%02d', $self->$_), qw( year month day ) } } use Test::Spec; describe MyDate => sub { it 'initializes from now' => sub { Time::Piece->expects('year')->returns(2019); Time::Piece->expects('mon')->returns(5); Time::Piece->expects('mday')->returns(16); my $md = 'MyDate'->new(); is $md->date, '20190516'; }; it 'initializes from date' => sub { my $md = 'MyDate'->new(date => 20190201); is_deeply [ $md->year, $md->month, $md->day ], [2019, '02', '0 +1']; }; it 'initializes from ymd' => sub { my $md = 'MyDate'->new(year => 2019, month => 11, day => 3); is $md->date, '20191103'; }; }; runtests();
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Moo different parameters for constructor
by LanX (Archbishop) on May 16, 2019 at 11:09 UTC
    Hi williamcharles

    The Moo docs suggest using BUILDARGS to adjust the construction process.

    There you're free to react to different parameter formats.

    HTH! :)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery FootballPerl is like chess, only without the dice

Re: Moo different parameters for constructor
by tobyink (Abbot) on May 17, 2019 at 14:00 UTC

    As others have suggested, BUILDARGS will do the job. Personally, I think you should consider:

    1. my $date = MyDate->now; 2. my $date = MyDate->new(year => $y, month => $m, day => $d); 3. my $date = MyDate->parse("$y$m$d");

    There's no reason to force them all to happen through the same constructor method. #1 and #3 would just be wrappers for #2.

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (3)
As of 2019-05-25 04:52 GMT
Find Nodes?
    Voting Booth?
    Do you enjoy 3D movies?

    Results (151 votes). Check out past polls.

    • (Sep 10, 2018 at 22:53 UTC) Welcome new users!