Created
March 17, 2021 11:10
-
-
Save j1n3l0/349cb42fd3329fc6e672b4251b41cb7e to your computer and use it in GitHub Desktop.
Trying to avoid mutating a readonly attribute
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use Test2::V0 -target => 'Client'; | |
subtest 'A class with a [readonly, class_type("URI")] attribute' => sub { | |
my $uri = 'https://example.com'; | |
my $object = $CLASS->new( service_uri => $uri ); | |
is( | |
$object, | |
object { call service_uri => $uri }, | |
'should return the attribute unchanged', | |
); | |
$object->get('path'); | |
is( | |
$object, | |
object { call service_uri => $uri }, | |
'should not update the attribute at a distance', | |
); | |
}; | |
done_testing(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package Client; | |
use Moo; | |
use Type::Utils qw< class_type >; | |
use Types::Standard -types; | |
use URI; | |
use experimental qw< signatures >; | |
has service_uri => ( | |
coerce => 1, | |
is => 'ro', | |
isa => class_type('URI')->plus_constructors(Str, 'new'), | |
); | |
sub get ( $self, $path ) { $self->service_uri->clone->path_segments($path) } | |
1; |
Interestingly, the Object::Pad solution has the same limitations:
use Object::Pad 0.36;
package Client;
class Client;
use Type::Utils qw< class_type >;
use Types::Standard -types;
use URI;
my $Uri = class_type('URI')->plus_constructors(Str, 'new');
has $service_uri :reader;
BUILD (%args) { $service_uri = $Uri->coerce( $args{service_uri} ) };
method get($path) { $service_uri->clone->path_segments($path) }
1;
You could use the around
method modifier to ensure that you are only ever passed a clone on the original object:
package Client;
use Moo;
use Type::Utils qw< class_type >;
use Types::Standard -types;
use URI;
use experimental qw< signatures >;
has service_uri => (
coerce => 1,
is => 'ro',
isa => class_type('URI')->plus_constructors(Str, 'new'),
);
around service_uri => sub ( $orig, $self ) {
$self->$orig()->clone();
};
sub get ( $self, $path ) {
$self->service_uri->path_segments($path);
}
1;
The same solution exists for Object::Pad:
use Object::Pad 0.36;
package Client;
class Client;
use Class::Method::Modifiers qw< around >;
use Type::Utils qw< class_type >;
use Types::Standard -types;
use URI;
my $Uri = class_type('URI')->plus_constructors(Str, 'new');
has $service_uri :reader;
BUILD (%args) {
$service_uri = $Uri->coerce( $args{service_uri} );
};
around service_uri => sub ( $orig, $self ) {
$self->$orig()->clone();
};
method get($path) {
$self->service_uri->path_segments($path);
}
1;
But the correct way to solve this with Object::Pad (as of v0.51) is:
use Object::Pad 0.51;
package Client;
class Client;
use Type::Utils qw< class_type >;
use Types::Standard -types;
use URI;
my $Uri = class_type('URI')->plus_constructors(Str, 'new');
has $service_uri :param;
ADJUST { $service_uri = $Uri->assert_coerce($service_uri) };
method service_uri () { $service_uri->clone() }
method get ($path) { $self->service_uri->path_segments($path) }
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In this instance you would have to clone the object otherwise the attribute will get mutated. Other ways around it would be to assign the attribute to a variable and use that.