Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Moose – Retain the original value of an attribute in a second attribute

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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)
    }
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading