gcbenison
gcbenison

Reputation: 11963

Compile-time sanity check provided by role

I have a module that refuses to load unless a compile-time sanity check is met. Something like this:

package TopSecret;
use Moose;

die "Only Joe can use this!" unless $ENV{USER} eq 'joe';

1;

Now I would like to apply a similar sanity check to multiple modules, so my thought is to put it in a role. The consuming module would provide some information to customize the check a bit. So it might look something like:

package TopSecret;
use Moose;
with 'ForAuthorizedUser';

sub authorized_user { 'joe' }

1;

The problem is: how can I exercise TopSecret::authorized_user() from within ForAuthorizedUser, at compile time? Something like 'requires "authorized_user"' - except it would have to verify not just that the method exists, but execute it and check the return value.

Upvotes: 2

Views: 133

Answers (1)

Hunter McMillen
Hunter McMillen

Reputation: 61510

I think that attribute overriding would be appropriate here. You declare the attribute in your Role and mark it as required, but don't provide a definition. Then the module that consumes the Role can supply the value for that attribute. Note that validation is typically done in the BUILD() subroutine.

package ForAuthorizedUser;
use Moose::Role;

use Carp qw(croak); # so you can see the line it fails on

has 'authorized_user' => (
   is       => 'ro',
   required => 1,
);

sub BUILD {
   my ($self) = @_;

   croak "Only Joe can use this!" 
      unless $self->authorized_user eq 'joe';
}

1;

Now in your module that consumes ForAuthorizedUser, you supply the definition for the attribute:

package TopSecret; 
use Moose;
with qw(ForAuthorizedUser);

has '+authorized_user' => (
   default => 'joe',
);

__PACKAGE__->meta->make_immutable;

In a separate module you do the same thing, but with a different name (mine):

package TopSecret2; 
use Moose;
with qw(ForAuthorizedUser);

has '+authorized_user' => (
   default => 'hunter',
);

__PACKAGE__->meta->make_immutable;

Then you could test this like so:

use TopSecret; 
use TopSecret2; 

TopSecret->new; # lives
TopSecret2->new # croaks Only Joe can use this! at constructor TopSecret2::new (defined at Test.pm line 35) line 36.

Upvotes: 1

Related Questions