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.
Related
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.
Suppose I want a class like this:
package Restraint;
use Moose;
has ball => (
is => 'ro',
does => 'Heavy',
);
has chain => (
is => 'ro',
does => 'Lockable',
);
has ball_class => (
is => 'ro',
isa => 'Str',
);
has chain_class => (
is => 'ro',
isa => 'Str',
);
1;
Now I want to instantiate the class like:
my $r = Restraint->new(
ball_class = 'Ball',
chain_class = 'Chain',
);
both arguments being mandatory.
I also want to instantiate both ball_class and chain_class during construction and assign them to corresponding attributes so that finally I can e.g. $r->ball->lift or $r->chain->lock etc.
How should I do that?
Just mark them both "required" - see Moose::Manual::Attributes
That's not going to do anything for "ball" or "chain" though. There's no connection.
You could set up ball/chain with lazy builders which would reference your classnames (see the Moose Manual again).
I'd probably just make the objects required and pass them in directly though:
my $r = Restraint->new(
ball => CannonBall->new(...),
chain => IronChain->new(...)
);
OK - you don't want to pass in objects. In that case you want lazy builders. Mark the ball as lazy and give it a builder and Moose will call the builder when the ball is first used.
http://metacpan.org/pod/Moose
http://metacpan.org/pod/Moose::Manual::Attributes
http://metacpan.org/pod/Moose::Cookbook::Basics::Recipe3
Do take the time to read the moose documentation - there's a lot of it, but it's quite well written and covers plenty of features. Lots of examples.
package Restraint;
use Moose;
has ball => (
is => 'ro',
does => 'Heavy',
lazy => 1,
builder => '_build_ball'
);
has ball_class => (
is => 'ro',
required => 1
);
sub _build_ball {
my $self = shift;
my $ball_class = $self->ball_class;
return $ball_class->new();
}
Using BUILD for instantiation while setting required only for *_class attributes seems to work to some extent, except that:
I had to make ball and chain read-write
Code:
package Restraint;
use Moose;
has ball => (
is => 'rw',
does => 'Heavy',
);
has chain => (
is => 'rw',
does => 'Lockable',
);
has ball_class => (
is => 'ro',
isa => 'Str',
required => 1,
);
has chain_class => (
is => 'ro',
isa => 'Str',
required => 1,
);
sub BUILD {
my $self = shift;
my $ball = $self->ball_class->new();
$self->ball( $ball );
my $chain = $self->chain_class->new();
$self->chain( $chain );
}
1;
It's not a big sacrifice for now, but I still wonder if there is more correct way, though.
Is there anyway to get $self into a MooseX::Types coercion? I have other data in the object that I want to use to seed my coercion from a String to an Object. Alternatively, is there anything like Class::MOP's initializer that will permit me to do this -- it would have to fire before the type checks.
Requested pseudo code:
with 'DBHandle';
has 'database' => ( isa => 'Str', is => 'ro', default => 'Db' );
has 'schema' => ( isa => 'Str', is => 'ro', default => 'schema' );
has 'table' => ( isa => 'Str', is => 'ro', default => 'column );
has 'columns' => ( isa => DBCols, is => 'ro', default => sub {[qw/foo bar baz/]} );
Here, I want "columns" to coerce to a DBCols -- an ArrayRef of DBCol's (objects) -- requiring the use of catalog, schema, and col attributes found in the class, and with a dbh/singleton provided by DBHandle.
To make this less-pseudo, the actually situation is only slightly more complex. I was able to accomplish the above with around, now what I want I to do is create an attribute trait that would permit this syntax:
has 'column_id' => (
isa => Int
, is => 'ro'
, traits => ['DBKey']
, default => 5
, column => 'foo'
);
Where the attribute trait column provided by DBKey, coerces to DBCol the same way that the above columns would: this would require the ability to access the classes database, schema, table, and again the singleton for the dbh.
No. It'd be nice, but coercions are really designed to be global, and no one has written a "context-sensitive coercion" yet, because no one's really sure how to. (Actually, s/coercions/type constraints/ -- it'd be useful just to say "this Str must be a valid column name, defined as an entry in this object's columns HashRef".)
People usually solve this problem with around and/or some combination of BUILD and BUILDARGS.
In Moose you can declare a group of attributes at once, assuming the initialization parameters are the same:
has [qw( foo bar baz )] => (
is => 'ro',
isa => 'Str',
required => 1,
);
This is a lovely feature that saves a ton of typing. However, I find myself puzzled about how define a predicate, clearer or even a builder method using this syntax.
has 'foo' => (
is => 'ro',
isa => 'Str',
required => 1,
clearer => 'clear_foo',
predicate => 'has_foo',
);
Is there a parameter I can use that will build standard 'has_X, 'clear_X and _build_X methods for all the attributes in my list?
has $_ => (
is => 'ro',
isa => 'Str',
required => 1,
clearer => '_clear_' . $_,
# etc...
) for (qw(foo bar baz);
Note that lazy_build => 1 will automatically generate clearers, and
predicates, but they will always be public, which is starting to be frowned
upon in the Moose community. (I don't think anyone's blogged about this yet,
but it's been a topic of conversation on IRC #moose of late.)
lazy_build => 1,
That also marks them lazy, but that's recommended for attributes with a builder.
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.