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?
CodePudding user response:
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)
}
CodePudding user response:
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.