Search code examples
perlmoose

Can I use an attribute modifer in Moose in a base class to handle multiple attributes from sub classes?


I've got several Modules that have a number of date time related attributes that accept a datestamp and return a human readable string ( dd Mon yyyy hh:mm:ss ).

package ModuleOne;

use Moose;

extends 'ModuleBase';

has date_attr_one => ( ... );

and then ...

package ModuleTwo;

use Moose;

extends 'ModuleBase';

has date_attr_mouse => ( ... );

As it's currently working I'm using an 'around' attribute modifier so that if there's no parameter return the a date in the format of '03 May 2012 12:33:42'. If there is a parameter in the format of '03 May 2012 12:33:42' the I want to set that as a datestamp.

So, in ModuleOne, there would be:

has date_attr_one => ( ... );

followed by:

around 'date_attr_one' => sub { ... }

Same type of thing occurs in the second module.

The problem I'm having is that there are multiple modules doing this and each of them is using the 'around' modifier and the same duplicated code. I'd like to move that around modifier into the base class so every Module that extends the base class can use that modifier. I tried putting it into the base class using a regex like: (in the base class)

around qr/date_attr_one/ => sub { ... }

I put some print statements in there that never executed. Roles don't allow such a thing.

Is there a way to move that around modifier into the base class so every Module that extends the base class can use that modifier where the attributes would have different names in each model?

So, as in the example above, the base class' around attribute modifier would need to handle $self->date_attr_one and $self->date_attr_mouse, among others.


Solution

  • If you create a package like the one below, all you need in your classes is:

    has date => (
       is     => 'rw',
       isa    => 'My::Date',
       coerce => 1,
    );
    

    Example code:

    my $o = My::Class->new(date => '03 May 2012 12:33:42');
    say $o->date();
    say 0+$o->date();
    
    $o->date(1336062822);
    say $o->date();
    say 0+$o->date();
    

    The aforementioned module:

    package My::Date;
    
    use strict;
    use warnings;
    
    use Moose;
    use Moose::Util::TypeConstraints;
    
    use DateTime::Format::Strptime qw( );
    use POSIX                      qw( strftime );
    
    my $format = DateTime::Format::Strptime->new(
       pattern   => '%d %b %Y %H:%M:%S',
       locale    => 'en_US',
       time_zone => 'local',
       on_error  => 'croak',
    );
    
    has epoch => (
       is       => 'rw',
       isa      => 'Num',
       required => 1,
    );
    
    sub as_string {
       return strftime('%d %b %Y %H:%M:%S', localtime( $_[0]->epoch ));
    }
    
    coerce __PACKAGE__,
       from 'Num',
          via { __PACKAGE__->new( epoch => $_ ) },
       from 'Str',
          via { __PACKAGE__->new( epoch => $format->parse_datetime($_)->epoch ) };
    
    use overload (
       '""' => sub { $_[0]->as_string },
       '0+' => sub { $_[0]->epoch },
       fallback => 1,
    );
    
    1;