tags:

views:

210

answers:

1

I'm looking for a way to set up some helper methods from within a parent Moose class, rather than a standalone utility class. If it is possible, it would be a more transparent way of adding Moose sugar to modules, as it does not require explicitly requiring any helper modules (as everything would come via the extends declaration).

Based on the example provided in the documentation, this is roughly what I am going for:

package Parent;

use Moose;

Moose::Exporter->setup_import_methods(
    with_meta => [ 'has_rw' ],
    as_is     => [ 'thing' ],
    also      => 'Moose',
);

sub has_rw {
    my ( $meta, $name, %options ) = @_;
    $meta->add_attribute(
        $name,
        is => 'rw',
        %options,
    );
}

# then later ...
package Child;

use Moose;
extends 'Parent';

has 'name';
has_rw 'size';
thing;

However this does not work:

perl -I. -MChild -wle'$obj = Child->new(size => 1); print $obj->size'
String found where operator expected at Child.pm line 10, near "has_rw 'size'"
        (Do you need to predeclare has_rw?)
syntax error at Child.pm line 10, near "has_rw 'size'"
Bareword "thing" not allowed while "strict subs" in use at Child.pm line 12.
Compilation failed in require.
BEGIN failed--compilation aborted.

PS. I've also tried moving the export magic into a role (with Role; rather than extends Parent;) but the same errors occur.

+7  A: 

This isn't supported, and there is a good reason why. A Class or a Role are not the same as sugar methods, and at some level different things should be different. If your problem is having to "use" Moose + A Custom Sugar package then you can solve that by simply having your custom sugar package export Moose as well, stealing from your example:

package MySugar;
use strict;
use Moose::Exporter;

Moose::Exporter->setup_import_methods(
    with_meta => [ 'has_rw' ],
    as_is     => [ 'thing' ],
    also      => 'Moose',
);

sub has_rw {
    my ( $meta, $name, %options ) = @_;
    $meta->add_attribute(
        $name,
        is => 'rw',
        %options,
    );
}

Then you simply say:

package MyApp;
use MySugar; # imports everything from Moose + has_rw and thing    
extends(Parent);

has_rw 'name';
has 'size';
thing;

This is how MooseX::POE works, as well as several other packages. I for one would argue against having extends bring in the sugar like you're suggesting here because a Class is not a bundle of sugar functions, and the two really should never be confused.

UPDATE: To bring in both at once the cleanest approach is to rework Parent as a role that is applied to Moose::Object.

package Parent::Methods;
use 5.10.0;
use Moose::Role;

sub something_special { say 'sparkles' }

Then we simply change the call to Moose::Exporter in MySugar to look like

Moose::Exporter->setup_import_methods(
    apply_base_class_roles => 'Parent::Methods',
    with_meta              => ['has_rw'],
    as_is                  => ['thing'],
    also                   => 'Moose',
);

Now you can simply say

package MyApp;
use MySugar; 

has_rw 'name';
has 'size';
thing;

package main;
MyApp->new->something_special; # prints sparkles

Which is I believe the final detail you were wanting.

perigrin
What I was looking for is to combine `Parent` and `MySugar` together into one class, since the sugar is specifically designed to build on components defined in `Parent`. However it's not the end of the world to define them and bring them in separately.. I just don't want to separate them as two separate components because they aren't. They should be brought in together or not at all.
Ether
Yup, Moose::Exporter will let you bring both in together. Defining them both in one class is a bad idea in my opinion because it conflates two different things that really should be separate.
perigrin
I've updated the answer to include an example of bringing in both together using the reccomended style. If you want more than this, I suggest coming past #moose on irc.perl.org or asking the Moose mailing list for specific questions.
perigrin
@perigrin: as it turns out I had to rework my entire structure due to a limitation with class attributes and inheritance, so now I'm not using 'extends' at all but bringing in all functionality with roles. I cannot use apply_base_class_roles as I'm using parameterized roles; however it's not inelegant to simply do `use MySugar; with 'Role::blah' => { params... };`. :)
Ether
@Ether you might talk with the people on #moose about allowing Paramaterized Roles to work as Traits, I'm not sure what the limitation there is but someone might have an elegant solution.
perigrin