How do I create a custom write accessor when using Moose? - perl

Edit: Answer added below, question left here for historical purposes only.
Moose documentation states that:
If you want, you can also explicitly specify the method names to be
used for reading and writing an attribute's value. This is
particularly handy when you'd like an attribute to be publicly
readable, but only privately settable. For example:
has 'weight' => (
is => 'ro',
writer => '_set_weight',
);
This might be useful if weight is calculated based on other methods.
For example, every time the eat method is called, we might adjust
weight. This lets us hide the implementation details of weight
changes, but still provide the weight value to users of the class.
Based on this example, I wrote the following code:
has '_current_url' => (
is => 'ro',
isa => 'URI',
writer => '_write_URI_to_current_url',
);
# Thus we ensure only a URI object gets written to current_url always
sub _current_url
{
my $self = shift;
$self->_write_URI_to_current_url(URI->new_abs($_[0], $self->start_url));
}
My intention was to ensure that setting current_url always sets it to a URI object even if it was called with a simple string. However, when I try to run the code, I get:
Cannot assign a value to a read-only accessor of _current_url
at the place (within my class) where I'm trying to set the current_url (with $self->_current_url($str);).
This is my second day with Moose so I'm quite confused as to what's going on here. To my understanding the is => 'ro' only asks Moose not to create a default write accessor with the same name as the attribute, is that understanding correct? How can I achieve my goal here?

Ok, I believe I've found the issue.
The _current_url method I've pasted above got overridden by Moose's generated read-only accessor of the same name, so when I tried to call $self->_current_url with a value, it throws the above error to indicate that the read-only accessor cannot set values.
I guess the error message should ideally be Cannot assign a value **through** a read-only accessor of _current_url, not **to** a read-only accessor.
Changing the name of the sub to _set_current_url solved the problem. I guess another way to achieve the same would be to tell Moose that _current_url is => 'rw' and then create an around '_current_url'. I haven't tried this approach.

Late to the game but just saw this post and ran into the same thing a while back. '_set_current_url' really seems like an attribute accessor instead of an attribute. Might want to consider:
has '_current_url' => {
is => 'rw',
isa => 'URI',
writer => 'set_current_url',
reader => 'get_current_url'
}
A bit cleaner this way since the attribute is the original '_current_url' and you have accessors to get/set the attribute.

Related

How is it not a StackOverflowException when a class has a field of the same class?

If a class T has a field of type T, how is there not a StackOverflow exception? Because if you create an object of T, there will be another T object inside of it, and that T object will in turn have its own T object, and on and on and on.
Okay I know this question is very confusing, and this is probably not the website to answer these types questions, but I'm just curious.
Edit:
Okay so here is an example to make myself more clear on what I mean:
class T {
T t = new T();
}
So if you create an object of T, you will be also creating another object of T, which in turn will have its own object of T, and so on and so forth. What I am asking is why is that not a StackOverflow, since an infinite chain of T's will be created?
I don't know if that helped.
Generally, in most (all?) programming languages, objects with properties that are complex data items do not store those full data items within their allocated space, rather they allocate enough space for a pointer or reference to that object. To use Perl terms:
package T;
use Moo;
use Types::Standard -all;
has ref_to_t => (is => 'ro', isa => InstanceOf['T']); # notice not "required"
1;
Then with:
my $obj1 = T->new;
my $obj2 = T->new(ref_to_t => undef);
my $obj3 = T->new(ref_to_t => $obj2);
This demonstrates three ways the situation does not lead to Armageddon:
$obj1 has (because this is how Perl's Moo works) no space allocated at all for the ref_to_t.
$obj2 has space allocated, but only enough for a reference to another object, that is null or undefined.
$obj3 has also space allocated, also only for a reference.

Using sub reference with Moose builder

Moose documentation mentions that builder property for attribute in class definiton should be a string that contains name for function that will be called to build relevant attribute. Simple testing however shows that sub reference works as well:
has 'some_attribute' => (
is => 'ro',
lazy => 1,
builder => sub {
require SomeModule::Heavy;
return SomeModule::Heavy->new($_[0]);
},
);
Did I miss something in docs? Is usage of sub reference officially supported for builder?
Moose manual says:
You can also provide a subroutine reference for default. This reference will be called as a method on the object. […] As an alternative to using a subroutine reference, you can supply a builder method for your attribute. This has several advantages. First, it moves a chunk of code to its own named method, which improves readability and code organization. Second, because this is a named method, it can be subclassed or provided by a role.
So, if you use subroutine reference for builder then you lose these advantages. I think subroutine reference works as side effect and has no practical application.

How can I acces read-only attributes of Moose objects?

I'm an absolute newbie to Moose and so far I have read Moose and most of the Cookbook.
There a few things I don't get. I created the following package:
package MyRange;
use Moose;
use namespace::autoclean;
has [ 'start', 'end' ] => (
is => 'ro',
isa => 'Int',
required => 1,
);
__PACKAGE__->meta->make_immutable;
1;
Then:
use MyRange;
my $br = MyRange->new(
start => 100,
end => 180
);
Now I can access my fields using e.g. $br->{start}, but I can also modify them (although they are "read only") using e.g. $br->{start}=5000. I can also add new keys like $br->{xxx}=111.
Am I missing anything? Isn't the object protected in some way? What's the meaning of ro?
When you said is => 'ro' you told Moose to create read-only accessors for you, that is, a reader method. You call that as
$br->start;
or
$br->end;
Setting the attributes using those methods will result in an exception:
$br->start(42);
If you had used is => 'rw', then the above would work and update the attribute's value.
What you're doing is direct hash access on the object, which violates encapsulation and shouldn't ever be necessary when using Moose.
The Moose manual, i.e. all the documents under the Moose::Manual namespace explain that in detail. A good starting point for questions like this is probably Moose::Manual::Attributes.
When you access to the attribute with $br->{start}, you are bypassing the accessor and you are adressing directly the underlying Moose implementation. You can do it, but you are not supposed to. Also, if Moose changes the implementation, your code will break.
You should instead access the attribute using the accessor method:
my $start = $br->start;
When you say that the attribute is 'RO', it means you are not allowed to change the attribute value using the accessor:
$br->start(32);

Rebuilding lazily-built attribute when an underlying attribute changes in Moose

I've got a Moose class with a lazy_build attribute. The value of that attribute is a function of another (non-lazy) attribute.
Suppose somebody instantiates the class with a value of 42 for the required attribute. Then they request the lazy attribute, which is calculated as a function of 42. Then, they have the nerve to change the first attribute!
The lazy one has already been built, so the builder will not get called again, and the lazy attribute is now out-of-date.
I have a solution now where I maintain a "dirty" flag on the required attribute, and an accessor on the lazy one checks the dirty flag and rebuilds it if needed.
However, this seems like a lot of work. Is there a way to handle this within Moose, e.g. using traits?
My typical solution:
has 'attr1' => (
...
trigger => \&clear_attr2,
);
i.e. when attr1 is updated, attr2 is cleared and will be rebuilt when it is next accessed. clear_attr2 comes for free when you use lazy_build. As long as you use the accessor methods, you don't need a 'dirty' flag.
This is a common pattern - some kind of trait to handle 'derived' attributes would be nice.

How can I access a method of the consumer class inside a method created at runtime in a method of the parameterized role using Moose with Perl?

I define a method inside a parametrized role that needs to create a new class at run time
using Moose::Meta::Class->create and apply that exact parametrized role to it. I am also making a new method for that role using
$new_class->meta->add_method( some_name => sub {
my ($self) = #_;
...
})
inside the sub {...} I want to access a method of the consumer class and use it for something, I have tried using $self->get_method, it didn't work, how do I do this?
Please notice that the $self inside the sub above is MooseX::Role::Parameterized::Meta::Role::Parameterizable
I also have another question, if I do this:
my $object = Moose::Meta::Class->create(
"some_type",
);
Why isn't $object of type some_type and it's some ugly MooseX::Role::Parameterized::Meta::Role::Parameterizable and how do I get to the object of type some_type?
To answer your second question, the reason is because Perl's OO doesn't allow you to add a method to just one instance of a class, so Moose has to fake it by creating a subclass with the extra method and reblessing the unique object into that subclass.
Note that, if you are doing things correctly and doing your introspection with isa, has, and/or does rather than by trying to rely on the name of the object's blessed package, this doesn't matter. The object still isa some_type, has all of some_type's attributes, and does all of some_type's roles even though it's now blessed into a package with an ugly auto-generated name.
It sounds like your underlying problem is nearly exactly what I described at this question: from within the role definition, you need to get at the class (and its meta-class) of the object or class the role is being applied to. This isn't possible from within normal roles, but it's possible through parameterized roles.
I'm not quite sure what you're trying to do here. Let's assume you have
my $new_class = Moose::Meta::Class->create('FooBar');
then $new_class is the meta object for FooBar. So, if you want to add a method to FooBar you would say
$new_class->add_method(foo => sub { … });
which would basically be the same as
FooBar->meta->add_method(foo => sub { … });
You should also probably use find_meta() from Moose::Util. This will return the correct meta object (if there is one) even if your class doesn't have a meta method or it uses it for something else.
As said, I'm not sure this answers your question.