Question

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); 
}
Was it helpful?

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

OTHER TIPS

I don't know of any way of binding the two attributes together like you have with the direct hash assignments.

I'd probably have two lazy builders do something like:

sub _build_token {
  my $self = shift;
  my ($t, $ts) = $self->_get_authorized_token();
  $self->token_start($ts);
  return $t
}

And then the reverse for building token_start.

What you really want I suspect is to make token/token_start part of their own object. That way you can guarantee both are set together appropriately.


i will still have 2 dependant attributes together, i can't them separate there too. Or where is the point?

I'm not sure the point is clear from the question. The two values seem to me to belong together, or at least token_start depends on token. I would prefer to use $self->auth->token so that the linkage is clear.

If you want to skip that reference to the "auth" object though, just use handles

When faced with something of this nature -- two or more attributes whose values are generated at once -- and there's no compelling reason to create a little class to handle this, I usually create one attribute and then delegated accessors to access the results. e.g.:

has _token_info => (
    traits => ['Hash'],
    is => 'ro',
    isa => 'HashRef',
    builder => '_build__token_info',
    handles => {
        token => [ get => 'token' ],
        token_start => [ get => 'token_start' ]
    },
);

sub _build__token_info {

    # ... whatever needs to be done to get $token{,_start}
    return { token => $token, token_start => $token_start };
}

That way the first time someone accesses either token() or token_start(), the token and start value are generated and passed along.

Note this approach generally works best with values that are always either built or set privately within the class, not when one expects to build the class passing token and token_start to new().

see also http://whitepointstarllc.com/2012/05/simulating-multiple-lazy-attributes/

I got some idea myself too. Maybe instead of using method with returning values, i should use setter method, which returns one (if any) value but sets 2? Like this:

sub _set_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 => '' );
    $self->{token_start} = $ref->{Notification_Datetime};
    $self->{token} = $ref->{Notification_Data}{body}{token};
    return ($self->{token});    
}

Are there some pitfalls which i did notice?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top