gwg has asked for the wisdom of the Perl Monks concerning the following question:

Greeting monks,

I'm using Moose for the first time, and would appreciate some expert advice on some issues I'm running into.

I have a set of attributes with custom accessors, methods, etc.. I would like to group them in a single hash structure so that I can set/get/clear them globally (and keep my object nice and neat). I think I could do this by creating a new class with these various attributes in it, but I don't want to do that for a number of reasons. I'm stuck on how to move these attributes into a single place.

To illustrate, the object code currently looks like this:

package My::New::Object; has max_chunk => (is=>'rw', isa=>'Int', init_arg => 'size', clearer=>' +clear_max_chunk'); has liberal_mode => (is=>'rw', isa=>'Bool', default=>sub{1}); has fh => (is=>'rw', isa=>'My::New::Object::ProtoFileHandle', clearer= +>'clear_fh', predicate=>'has_fh', writer=>'_set_fh', coerce => 1, ini +t_arg => 'file', trigger => \&reset_temporary_variables ); [... more attributes, code, etc. ]

And dumping it gives this:

object: $VAR1 = bless( { 'liberal_mode' => 1, 'fh' => bless( \*Symbol::GEN0, 'FileHandle' ), [... other data ...] }, 'My::New::Object' );

I would like it to look like this:

object: $VAR1 = bless( { 'options' => { 'liberal_mode' => 1, 'fh' => bless( \*Symbol::GEN0, 'FileHandle' ), }, [... other data ...] }, 'My::New::Object' );

so that I could have methods like 'set_all_options' and 'clear_options' to deal with everything in the options hash, as well as preserving the various methods I already have associated with the existing attributes.

Any wisdom would be greatly appreciated! Thanks.

Replies are listed 'Best First'.
Re: Moose question
by stvn (Monsignor) on Aug 28, 2009 at 20:11 UTC

    There is no simple way to do what you ask and still be able to define the attributes as you currently are. This is obviously because Moose is managing the instance data structure for you. What you really want is a sub-object, but ...

    I think I could do this by creating a new class with these various attributes in it, but I don't want to do that for a number of reasons.

    What might those reasons be? Because what your looking to do is exactly that, encapsulate all these items into a sub-object. You could even us a combination of delegation and coercion to make it seem like you aren't using a sub-object at all.

    package My::New::OptionObject; use Moose; use Moose::Util::TypeConstraints; coerce 'My::New::OptionObject' from 'HashRef' via { My::New::OptionObject->new( %{ $_ }) }; has max_chunk => ( is=>'rw', isa=>'Int', init_arg => 'size', clearer=>'clear_max_chunk' ); has liberal_mode => ( is=>'rw', isa=>'Bool', default=>sub{1} ); has fh => ( is=>'rw', isa=>'My::New::Object::ProtoFileHandle', clearer=>'clear_fh', predicate=>'has_fh', writer=>'_set_fh', coerce => 1, init_arg => 'file', trigger => \&reset_temporary_variables ); package My::New::Object; use Moose; has options => ( is => 'ro', writer => 'set_all_options', isa => 'My::New::OptionObject', handles => [qw[ max_chunk liberal_mode fh ]], # and any others you + might want ... coerce => 1, lazy => 1, clearer => 'clear_options', default => sub { My::New::OptionObject->new } );
    The above code would allow you do things like:
    my $o = My::New::Object->new( options => { ... pass in the args to the options here ... } );
    This will coerce the options HASH into a My::New::OptionObject for you, and because of the delegation you can still call the max_chunk, liberal_mode and fh accessor methods on $o.
    $o->clear_options;
    This will clear the instance of My::New::OptionObject and because of default and lazy are there, the next time you try and call a method on it it will create a new (empty) My::New::OptionObject object.
    $o->set_all_options( { ... } );
    Since the option attribute's writer is called "set_all_options" and coerce is on, this will just work and create a new My::New::OptionObject object for you.

    So while you can't do exactly what your looking for in terms of changing the instance structure, we can "fake" it to some degree, which should be sufficient since you should be treating your objects as black boxes anyway :)

    -stvn
      There is no simple way to do what you ask and still be able to define the attributes as you currently are. This is obviously because Moose is managing the instance data structure for you. What you really want is a sub-object, but ...

      I thought that might be the case... it seemed like the kind of thing that you should be able to do without creating a new object, but I guess not. Thank you for your thorough answer, it's very helpful!

Re: Moose question
by james2vegas (Chaplain) on Aug 29, 2009 at 10:31 UTC
    You might want to look at MooseX::AttributeHelpers. It lets you create handlers for hashrefs and arrayref types (among other types), something like this (based on the example in the documentation):

    has 'options' => ( metaclass => 'Collection::Hash', is => 'rw', isa => 'HashRef[Str]', default => sub { {} }, provides => { exists => 'option_exists', kv => 'options', get => 'get_option', set => 'set_option', clear => 'clear_options', accessor => 'option', }, curries => { set => { set_quantity => [ 'quantity' ] } } );

    This lets you use the get_option and set_option methods, or option to use accessor syntax like $obj->option('test') to get an option or $obj->option( test => 'value') to set the value, and clear_options clears the whole hash. The curries hash lets you pass parameters to the handler methods in object methods, e.g. $obj->set_quantity(42) (using the code above) would be equivalent to $obj->set_option(quantity => 42)

    See MooseX::AttributeHelpers::MethodProvider::Hash for the options available for hash objects.