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?
There's no need to use BUILD
or BUILDARGS
.
Just use init_arg
to cause both attributes to be initialized from the same argument.
#!/usr/bin/perl
use v5.14;
use warnings;
{
package Foo;
use Moose;
has url => (
is => 'rw',
isa => 'Str',
required => 1,
);
has original_url => (
is => 'ro',
init_arg => 'url',
);
}
my $o = Foo->new( url => 'a' );
say $o->url; # a
say $o->original_url; # a
This approach makes it trivial and efficient to "backup" multiple attributes.
for my $name (qw( url ... )) {
has "original_$name" => (
is => 'ro',
init_arg => $name,
);
}
Whichever technique you use, you probably want to avoid isa
on the attribute for the original. There's no point in validating the input twice, and I suspect it would lead to confusing messages if you did.