Search code examples
perlmoosemoo

How to declare 2 dependant attributes in Mooseish way?


In my object constructor i had statement to initialize two attributes same time:

($self->{token}, $self->{token_start}) = $self->_get_authorized_token();

So i got token and it's starting time together in one statement.

Now i try to port my module to use Moo(se) and here i don't know how should i set those two bound attributes at same time?. Some pseudo code would be like that:

has qw/token token_start/ => (
    is  => 'rw',
    default => shift->_get_authorized_token(); 
);

But how to declare 2 bound attributes in Moo(se)ish way?


EDIT. I show the code of the method _get_authorized_token, maybe it will brings some ideas:

sub _get_authorized_token {
    my $self = shift;
    my $postData = { 'apikey' => $self->{key} };
    my $url = $self->{base_url} . '/seller';
    my $xml = $self->_post(url => $url,
                            postdata => $postData,
                        );
    my $ref = XMLin($xml, SuppressEmpty => '' );
    my $time = $ref->{Notification_Datetime};
    my $token = $ref->{Notification_Data}{body}{token};
    return ($token, $time); 
}

Solution

  • Once you end up with two attributes that are basically linked to the point where you always set them simultaneously ... the answer is usually to create a value object with two attributes for the purpose and then delegate the relevant methods to it. So, something like -

    package MyApp::TokenInfo;
    
    use Moo;
    
    has token => (is => 'ro', required => 1);
    has token_start => (is => 'ro', required => 1);
    
    ...
    
    package MyApp::ThingWithAToken;
    
    use Module::Runtime qw(use_module);
    use Moo;
    
    ...
    
    has token_info => (is => 'lazy', handles => [ qw(token token_start) ]);
    
    sub _build_token_info {
      my ($self) = @_;
      my ($token, $token_start) = $self->_get_authorized_token;
    
      # this is equivalent to:
      #
      #   require MyApp::TokenInfo;
      #   return MyApp::TokenInfo->new(...);
      #
      # but more concise
    
      return use_module('MyApp::TokenInfo')->new(
        token => $token,
        token_start => $token_start
      );
    }
    
    ...
    
    my $thing = MyApp::ThingWithAToken->new(...);
    
    $thing->token; # calls $thing->token_info->token;
    $thing->token_start; # calls $thing->token_info->token_start
    

    so the presence of the value object isn't required knowlede from externally but internally you've still got the two attributes tied together in a way that lets your implementation handle them as a single "thing".

    -- mst