I’m trying to create an object that will fetch a resource from the web, and needs to remember both where the resource was found eventually, as well as what the original URL was that we gave it.
I don’t want to have to specify the URL twice, nor do I want to have loads of conditionals every time I want to use the URL to figure out whether I should use the "url" attribute or the "updated URL" attribute or some such, so I thought I’d create a second attribute with default property set to initialize from the original URL:
package foo;
use Moose;
has 'url' => (
is => 'rw',
isa => 'Str',
required => 1,
);
has 'original_url' => (
is => 'ro',
isa => 'Str',
default => sub { shift->url },
);
If this would have worked (see below), then I would have been able to change the url at will, and just use the url attribute whenever I need to access the "current" URL; and then later on, if I need to see if the url was ever changed, I can just compare the url attribute against the original_url attribute, and go from there.
The problem with this, however, is that the order of initialization of these attributes seems to be not well defined; sometimes the default sub for the original_url attribute is called before the url property has a value, which means it will return undef, which obviously causes an error.
I thought of making original_url a lazy attribute, add a predicate and then add a trigger on url to update original_url to the old value if its predicate says it hasn’t been set yet, but it turns out that triggers are called after the value has been changed, so I don’t think I can do that.
Is there a method to accomplish what I’m trying to do that won’t cause errors?
>Solution :
What about setting the value in the BUILD?
#!/usr/bin/perl
use warnings;
use strict;
{ package Foo;
use Moose;
has url => (
is => 'rw',
isa => 'Str',
required => 1,
);
has original_url => (
is => 'ro',
writer => 'copy_url',
isa => 'Str',
init_arg => undef,
);
sub BUILD {
my ($self) = @_;
$self->copy_url($self->url);
}
}
my $o = 'Foo'->new(url => 'a');
$o->url('b');
$o->original_url eq 'a' or die;
You could do it in BUILDARGS, too, but I don’t think it’s the right place for such an action:
has _original_url => (
reader => 'original_url',
isa => 'Str',
);
around BUILDARGS => sub {
my ($orig, $class, %args) = @_;
$args{_original_url} = $args{url};
$class->$orig(%args)
}