Search code examples
perlmoose

Using BUILDARGS to substitute a class in a Role


I use a Perl Moose Role (Import::Git::Role) as an Abstract Base Class to share common behaviour between a Class an actual implementation of functionality (Import::Git) and a Class that performs some logging instead (Import::Git::dryrun).

I would like the dryrun class to be transparent. I want to create a object like this:

   my $git = Import::Git->new( dryrun => $dryrun );

The variable dryrun could be 0 or 1. If it is 1, I would like to construct an Import::Git::dryrun object instead, basically replacing the Import::Git object with it. Thats intended because they share all methods via the role.

I have tried to exchange the Object during the BUILDARGS Method like this:

around BUILDARGS => sub {                                                                                                                                                                                                                                                           
     my $orig  = shift;
     my $class = shift;

     my %args = ( @_ == 1 ? %{ $_[ 0 ] } : @_ );

     if ( !%args || $args{ 'dryrun' } != 1 ) {
         return $class->$orig( @_ );
     }
     else {
         return Import::Git::dryrun->$orig( @_ );
    }
};

but this doesn't achieve what I am trying to do, it constructs the old class:

  DB<1> x Import::Git->new( dryrun => 1 )
0  Import::Git=HASH(0x2fd9210)
   'dryrun' => 1
  DB<2> x Import::Git->new()
0  Import::Git=HASH(0x301dbb8)
   'dryrun' => 0
  DB<3> 

I thought I would probably have to call the new Method of the dryrun-method so I made the following exchange:

   # change this:
   return Import::Git::dryrun->$orig( @_ );
   # to this
   return Import::Git::dryrun->new( @_ ); 

But it returns BUILDARGS did not return a HASH reference.

What am I missing?


Solution

  • Having a constructor build a different class than the one requested is icky. I wouldn't take the approach you did even if it did work. I would use

    sub factory {
       my ($class, %opts) = @_;
       return $opt{dryrun} ? $class.'::dryrun' : $class;
    }
    
    Import::Git->factory( dryrun => $dryrun )->new( ... )
    

    or

    sub instantiate {
       my ($class, %opts) = @_;
       return ( delete($opt{dryrun}) ? $class.'::dryrun' : $class )->new(%opts);
    }
    
    Import::Git->instantiate( dryrun => $dryrun, ... )