How do I set a Moose read only attribute trait?
package AttrTrait;
use Moose::Role;
has 'ext' => ( isa => 'Str', is => 'ro' );
package Class;
has 'foo' => ( isa => 'Str', is => 'ro', traits => [qw/AttrTrait/] );
package main;
my $c = Class->new( foo => 'ok' );
$c->meta->get_attribute('foo')->ext('die') # ro attr trait
What is the purpose of Read Only attribute traits if you can't set it in the constructor or in runtime? Is there something I'm missing in Moose::Meta::Attribute? Is there a way to set it using meta?
$c->meta->get_attr('ext')->set_value('foo') # doesn't work either (attribute trait provided not class provided method)
You can set it in the constructor:
package Class;
has 'foo' => ( isa => 'Str', is => 'ro', ext => 'whatever', traits => ['AttrTrait'] );
You just need to pass it to the right constructor (the constructor for the attribute).
I use default to deal with ro attributes:
package Foo;
use Moose;
has 'myattr' => (is => 'ro', default => 'my value goes here');
And since you won't be setting myattr's value anywhere else, the default is used.
Related
I have a Moose class with some properties (x,y,z). I subclass it, and for the subclass, x is always 3. How can I specify this in subclass?
One could use BUILDARGS.
around BUILDARGS => sub {
my $orig = shift;
my $class = shift;
return $class->$orig(#_, x => 3 );
};
I used to work with Moo but it seems to be the same. You just need to declare the property in the subclass using + to override previous declaration.
package Foo;
use Moose;
has 'a' => (
is => 'rw',
isa => 'Num',
);
has 'b' => (
is => 'rw',
isa => 'Num',
);
has 'c' => (
is => 'rw',
isa => 'Num',
);
package My::Foo;
use Moose;
extends 'Foo';
has '+a' => (
default => 3,
);
I'm trying to get deep coercions work with Type::Tiny without any success. From the manual it's said that:
"Certain parameterized type constraints can automatically acquire
coercions if their parameters have coercions. For example:
ArrayRef[Int->plus_coercions(Num, q{int($_)}) ]
... does what you mean!"
What I try to accomplish is getting something like this "do what I mean":
package Person;
use Types::Standard -types;
use Moo;
has name => (is => 'ro', isa => Str);
package Family;
use Types::Standard -types;
use Moo;
has members => (is => 'ro', isa => ArrayRef[InstanceOf['Person']]);
package main;
my $family = Family->new(members => [
'mom',
Person->new(name => 'dad'),
Person->new(name => 'girl'),
'dog'
]);
When instantiating Family with elements that are a Str they should be automatically be coerced into Person objects. I've tried a range of different ideas (plus_coercions, Type libraries, etc) without any luck. They all fail in the same way.
When using plus_coercions (from Str to Object)
package Family;
has members => (
is => 'ro',
isa => ArrayRef[ Object->plus_coercions(Str, q{ Person->new(name => $_) }) ],
);
Type::Tiny throws an exception:
Reference ["mom",bless( {"name" => "dad"}, 'Person' ),bless( {"name" =...] did not pass type constraint "ArrayRef[Object]" (in $args->{"members"})
"ArrayRef[Object]" constrains each value in the array with "Object"
"Object" is a subtype of "Object"
"Object" is a subtype of "Ref"
Value "mom" did not pass type constraint "Ref" (in $args->{"members"}->[0])
"Ref" is defined as: (!!ref($_))
I know I could get around this by modifying the arguments to Family->new using a BUILDARGS sub in Family, but it would be neat if Type::Tiny could do that automatically.
Update
Thanks to Tobys friendly help, I got this working. The only part that troubled me a bit was the use of ArrayRef[Object] instead of the correct ArrayRef[InstanceOf['Person']] (InstanceOf doesn't have any plus_coercions). With Object an instance of any class could have been inserted into members, and that is certainly not what you want.
Got around that by making a class_type. Here's the full working code:
package Person;
use Types::Standard -types;
use Moo;
has name => (is => 'ro', isa => Str);
package Family;
use Types::Standard -types;
use Type::Utils -all;
use Moo;
my $Person = class_type { class => 'Person' };
my $Members = ArrayRef[
$Person->plus_coercions(Str, q{ Person->new(name => $_) })
];
has members => (
is => 'ro',
isa => $Members,
coerce => $Members->coercion,
);
sub list { join(', ', map { $_->name } #{ $_[0]->members }) }
package main;
my $family = Family->new(members => [
'mom',
Person->new(name => 'dad'),
Person->new(name => 'girl'),
'dog'
]);
print $family->list, "\n";
Which nicely prints mom, dad, girl, dog when run.
Moose/Moo/Mouse attributes don't coerce by default, so even though the type constraint has a coercion, you need to tell the attribute to use that coercion!
If you were using Moose or Mouse, you could do:
has members => (
is => 'ro',
isa => ArrayRef[ Object->plus_coercions(Str, q{ Person->new(name => $_) }) ],
coerce => 1,
);
But Moo doesn't support coerce=>1; instead it expects a coderef or overloaded object to act as the coercion. Type::Tiny can provide you with a suitable overloaded object by calling $type->coercion.
# Let's store the type constraint in a variable to avoid repetition...
my $type = ArrayRef[
Object->plus_coercions( Str, q{Person->new(name => $_)} )
];
has members => (
is => 'ro',
isa => $type,
coerce => $type->coercion,
);
I'm trying to write a perl module using Mouse, and after the object has been initialized, but before the user makes any calls, I need to initialize object1 with the two values from object2 and object3 that the user was required to give. I tried to use Mouse's after feature to have a subroutine called after new:
package Test;
use strict;
use Mouse;
has 'object1' => ( is => 'rw', isa => 'Any');
has 'object2' => ( is => 'ro', isa => 'Str', required => 1);
has 'object3' => ( is => 'ro', isa => 'Str', required => 1);
after 'new' => sub {
my ($self) = #_;
$self->object1(#do stuff with object2 and object3);
};
1;
However, currently I get this error:
Invalid object instance: 'Test' at lib/Test.pm line 18.
Is there a way I can initialize a value with user supplied values before the user gets the object reference returned to them?
Mouse is compatible with Moose. Object creation has the following phases:
Arguments are passed through the BUILDARGS method if one is defined. This can munge the arguments before Moose/Mouse touch them, e.g. to provide default arguments or to accommodate other calling styles than the keyword convention.
A new instance is created and the fields (declared by has) are populated.
The object is now set up and ready to use (from Mouse's/Moose's perspective). You might have a different idea of a correct setup, and can perform your checks in a BUILD method. This is the point where you can also perform remaining initialization that can't be expressed with has declarations.
So your example might become:
use strict;
use warnings;
package Your::Class;
use Mouse;
has 'object1' => ( is => 'rw', isa => 'Any');
has 'object2' => ( is => 'ro', isa => 'Str', required => 1);
has 'object3' => ( is => 'ro', isa => 'Str', required => 1);
sub BUILD {
my ($self) = #_;
$self->object1($self->object2 . $self->object3);
};
package main;
use Test::More;
# Your::Class->new(object2 => "foo", object3 => "bar");
my $instance = new_ok('Your::Class', [object2 => "foo", object3 => "bar"]);
is($instance->object1, "foobar");
done_testing;
To learn more about object construction in Moose and Moose-compatible object systems, read Moose::Manual::Construction.
Have:
package MyPath;
use strict;
use warnings;
use Moose;
has 'path' => (
is => 'ro',
isa => 'Path::Class::Dir',
required => 1,
);
1;
But want create this object with two ways, like:
use strict;
use warnings;
use MyPath;
use Path::Class;
my $o1 = MyPath->new(path => dir('/string/path')); #as Path::Class::Dir
my $o2 = MyPath->new(path => '/string/path'); #as string (dies - on attr type)
And when call it with a 'Str' - want convert it internally in the MyPath package to Class::Path::Dir, so, both: $o1->path and $o2->path should return blessed Path::Class::Dir
When I tried extend the definition to the next:
has 'path' => (
is => 'ro',
isa => 'Path::Class::Dir|Str', #allowing both attr types
required => 1,
);
It doesn't works and still need "somewhat" convert the Str to Path::Class::Dir automatically-internally in the package MyPath...
Could someone give me some hints?
EDIT: Based on Oesor's hint I found than i need someting like:
coerce Directory,
from Str, via { Path::Class::Dir->new($_) };
has 'path' => (
is => 'ro',
isa => 'Directory',
required => 1,
);
But still havent idea how to correctly use it...
Some more hints please?
You're looking for type coersion.
use Moose;
use Moose::Util::TypeConstraints;
use Path::Class::Dir;
subtype 'Path::Class::Dir',
as 'Object',
where { $_->isa('Path::Class::Dir') };
coerce 'Path::Class::Dir',
from 'Str',
via { Path::Class::Dir->new($_) };
has 'path' => (
is => 'ro',
isa => 'Path::Class::Dir',
required => 1,
coerce => 1,
);
Hint -- look for how to coerce the value:
https://metacpan.org/pod/Moose::Manual::Types
I'm using Moose to write an object module.
I currently have a few mandatory fields:
has ['length'] => (
is => 'ro',
isa => 'Int',
required => 1,
);
has ['is_verified'] => (
is => 'ro',
isa => 'Bool',
required => 1,
);
has ['url'] => (
is => 'ro',
isa => 'Str',
required => 1,
);
After the object was initialized with those fields, I would like to create some structure and use it from the object methods.
how (where) should I do that?
There are (at least) two possibilities:
You can create a BUILD sub. It gets called automatically after the object is initialized.
You create a normal attribute and mark it lazy. Then you provide a sub that creates this attribute: either builder or default. You can read more about this in the manual.