Search code examples
perlmoose

Moose structured types


I'd like to create a structured type in Moose that can be used as the type for another Moose attribute. For example, I'd like to be able to create a name attribute which has its own value and error attributes.

I would therefore like to know the best way of achieving this. I have created a working example by defining a simple Moose class to represent a generic Field object. This has the value and error attributes. I have then created another Moose class for the Person object. This has id and name attributes, both of which are of type Field:

Define a generic field object:

package MyApp::Type::Field;
use Moose;
use namespace::autoclean;

has 'value' => ( is => 'rw' );  
has 'error' => ( is => 'rw', isa => 'Str' );

__PACKAGE__->meta->make_immutable;
1;

Define a Person object which uses the field object:

package MyApp::Person;
use Moose;
use namespace::autoclean;
use MyApp::Type::Field;

has 'id'   => ( is => 'rw', isa => 'MyApp::Type::Field' );    
has 'name' => ( is => 'rw', isa => 'MyApp::Type::Field' );

__PACKAGE__->meta->make_immutable;
1;

Do something with the Person object:

package MyApp::Test;

use Moose;
use namespace::autoclean;
use MyApp::Person;

my $person = MyApp::Person->new();

# This works.
$person->id( MyApp::Type::Field->new() );
$person->id->value( 1 );
$person->id->error( 'Not found' );

# This fails as the name object has not yet been defined.
$person->name->value( 'Dave' );
# Can't call method "value" on an undefined value at ...

__PACKAGE__->meta->make_immutable;
1;

This works, but in MyApp::Test I would like to be able to directly access the value and error attributes of the person's name and id without first having to instantiate a new MyApp::Type::Field object for each of the person's attributes.

Or, to put it another way, I'd prefer it if the user of the Person class did not have to do this: $person->id( MyApp::Type::Field->new() ); before being able to use the id attribute.

Is there a nice clean way that I can achieve this?


Solution

  • Couldn’t you simply supply a default for the properties?

    has 'id' => (
        is => 'rw',
        isa => 'MyApp::Type::Field',
        default => sub { MyApp::Type::Field->new }
    );
    

    …or do the equivalent in BUILD.