Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Moose composing role with required attribute into existing instance

by tj_thompson (Scribe)
on Aug 03, 2012 at 18:10 UTC ( #985299=perlquestion: print w/ replies, xml ) Need Help??
tj_thompson has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks!

I'm back with another Moose question. Fortunately, this one's easy to lay out.

I have the following code:

package Point; use Moose; use namespace::autoclean; has 'x' => ( isa => 'Int', is => 'rw', required => 1 ); has 'y' => ( isa => 'Int', is => 'rw', required => 1 ); package Named; use Moose::Role; use namespace::autoclean; has 'name' => ( isa => 'Str', is => 'rw', required => 1 ); package main; use Moose::Util; my $p = Point->new( x => 1, y => 1 ); my $role = 'Named'; $role->meta->apply( $p ); # <-- line 41 print $p->dump;

This code fails with the following:

Attribute (name) is required at /nfs/pdx/disks/nehalem.pde.077/perl/5. +12.2/lib64/site_perl/x86_64-linux/Moose/Meta/Attribute.pm line 514 ...<snipping unnecessary trace info>.. Moose::Meta::Role::apply('Moose::Meta::Role=HASH(0x114b110)', 'Moose:: +Meta::Class::__ANON__::SERIAL::1=HASH(0xfc2748)') called at /nfs/pdx/ +disks/nehalem.pde.077/scripts/test3.pl line 41

The problem is that the 'name' attribute in the Named role is required. However, I've already created my Point instance before I compose the Named role into this. This problem goes away if you remove the required parameter from the 'name' attribute in the Named role.

Obviously, there are many workarounds to this issue. Disabling the required parameter and initializing the attribute after role application is one. Ensuring the attribute is already defined by the instance is another. Composing the role into the class prior to instantiation is a third.

I believe having to remove the required parameter from the attribute and relying on the programmer to remember to properly initialize the attribute is the wrong way to do this. Moose provides the required parameter explicitly to ensure that a given attribute is properly initialized. Ensuring the instance already has an attribute that satisfies the requirement seems to be a convoluted way to solve the issue, definitely worse than the first workaround. Composing the role into the class prior to instantiation limits flexibility as I am trying to apply differing roles to differing instances.

So, the summary: Is there a good way to compose a role with a required attribute into an instance other than what I've outlined above?

Thanks in advance for taking the time to read my question, it is always appreciated!

Comment on Moose composing role with required attribute into existing instance
Select or Download Code
Re: Moose composing role with required attribute into existing instance
by jandrew (Hermit) on Aug 03, 2012 at 18:48 UTC

    Two possibilities occur to me.

    One, compose the class and role together on the fly when you build the instance (instead of adding it to the instance after creation) so that when you need it you have it and when you don't you don't have to use it. This will allow your attribute to retain the required=>1 status. (This is my preference.)

    use Modern::Perl; use Moose::Util qw( with_traits ); my $point = with_traits( 'MyClass', ( 'Point', 'Named' ), )->new( x => 1, y => 1, name => 'my_point', );#To satisfy the required values

    If that doesn't seem to be a good fit then just add a default value and a setter (safer than 'rw') to your 'name' attribute. This will eliminate the error. You can then use your setter to change the value to the correct one when and where you want.

    has 'name' => ( isa => 'Str', is => 'ro',# Note the change required => 1, default => 'change_me', writer => 'set_name', );

    TIMTOWTDI applies to Moose too!

      The first solution is good. I create the instance at the same time that I apply my selected roles, so this works nicely. Syntax is a little ugly, but I've seen worse :) Thank you Jandrew for the help!
      I think the arguments to with_traits might be wrong though. I had to modify this:
      my $point = with_traits( 'MyClass', ( 'Point', 'Named' ), )->new( x => 1, y => 1, name => 'my_point', );#To satisfy the required values
      To this:
      my $point = with_traits( 'Point', 'Named', )->new( x => 1, y => 1, name => 'my_point', );#To satisfy the required values
      The first argument should be the base class you are composing roles into and all remaining arguments should be roles. I wouldn't have bothered correcting this here but maybe it will help someone else. Excellent solution.

        You are correct the syntax for the code you posted needs the class followed by roles. I initially misread Point as a Role. I tend to put all of the appended roles in a list form though just to improve readability (even if there is just one). Also if you don't like the method call on a method result you can separate them into two and the required=>1, elements of the attributes still won't error.

        my $named_point_class = with_traits( 'Point', ( 'Named', ), ); my $point_instance = $named_point_class->new( x => 1, y => 1, name => 'my_point', );
        Update:If you are using this in boilerplate the role can be a dummy name as well and it still works!

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://985299]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (13)
As of 2014-09-02 20:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite cookbook is:










    Results (29 votes), past polls