I have read various tutorials and the Moo documentation but I cannot find anything that describes what I want to do.
What I want to do is something like the following:
has 'status' => (
is => 'ro',
isa => Enum[qw(pending waiting completed)],
);
has 'someother' => (
is => is_status() eq 'waiting' ? 'ro' : 'rw',
required => is_status() eq 'completed' ? 1 : 0,
isa => Str,
lazy => 1,
);
If I'm just way off base with this idea, how would I go about making an attribute 'ro' or 'rw' and required or not, depending on the value of another attribute?
Note, the Enum is from Type::Tiny.
Ask yourself why you want to do this. You are dealing with objects. Those are data that has a set of logic applied to them. That logic is described in the class, and the object is an instance of data that has the class's logic applied.
If there is a property (which is data) that can have two different logics applied to it, is it still of the same class? After all, whether a property is changeable is a very distinct rule.
So you really have two different classes. One where the someother
property is read-only, and one where it is changeable.
In Moo (and Moose) there are several ways to build that.
someother
, and apply it in the constructor. Moo::Role inherits that from Role::Tiny.Here is an example of the approach that uses roles.
package Foo;
use Moo;
use Role::Tiny ();
has 'status' => ( is => 'ro', );
has 'someother' => (
is => 'ro',
lazy => 1,
);
sub BUILD {
my ( $self) = @_;
Role::Tiny->apply_roles_to_object($self, 'Foo::Role::Someother::Dynamic')
if $self->status eq 'foo';
}
package Foo::Role::Someother::Dynamic;
use Moo::Role;
has '+someother' => ( is => 'rw', required => 1 );
package main;
use strict;
use warnings;
use Data::Printer;
# ...
First we'll create an object that has a dynamic someother
.
my $foo = Foo->new( status => 'foo', someother => 'foo' );
p $foo;
$foo->someother('asdf');
print $foo->someother;
__END__
Foo__WITH__Foo::Role::Someother::Dynamic {
Parents Role::Tiny::_COMPOSABLE::Foo::Role::Someother::Dynamic, Foo
Linear @ISA Foo__WITH__Foo::Role::Someother::Dynamic, Role::Tiny::_COMPOSABLE::Foo::Role::Someother::Dynamic, Role::Tiny::_COMPOSABLE::Foo::Role::Someother::Dynamic::_BASE, Foo, Moo::Object
public methods (0)
private methods (0)
internals: {
someother "foo",
status "foo"
}
}
asdf
As you can see, that works. Now let's make a static one.
my $bar = Foo->new( status => 'bar', someother => 'bar' );
p $bar;
$bar->someother('asdf');
__END__
Foo {
Parents Moo::Object
public methods (4) : BUILD, new, someother, status
private methods (0)
internals: {
someother "bar",
status "bar"
}
}
Usage: Foo::someother(self) at /home/julien/code/scratch.pl line 327.
Ooops. A warning. Not a nice 'read-only' exception like in Moose, but I guess this is as good as it gets.
However, this will not help with the required
attribute. You can create a Foo->new( status => 'foo' )
without someother
and it will still come out ok.
So you might want to settle for the subclass approach or use a role and build a factory class.