tags:

views:

404

answers:

4

If you have an attribute that needs to be modified any time it is set, is there a slick way of doing this short of writing the accessor yourself and mucking around directly with the content of $self, as done in this example?

package Foo;
use Moose;

has 'bar' => (
    isa => 'Str',
    reader => 'get_bar',
);

sub set_bar {
    my ($self, $bar) = @_;
    $self->{bar} = "modified: $bar";
}

I considered trigger, but it seemed to require the same approach.

Is working directly with the hash reference in $self considered bad practice in Moose, or am I worrying about a non-issue?

+3  A: 

I think using the hash reference is fine within a trigger like this:

package Foo;
use Moose;

has 'bar' => ( 
    isa => 'Str', 
    is  => 'rw', 
    trigger => sub { $_[0]->{bar} = "modified: $_[1]" },
);

The trigger also fires when bar arg passed with the constructor. This won't happen if you define your own set_bar method or with a method modifier.

re: hash reference - Generally I think its best to stick with the attribute setters/getters unless (like with above trigger) there is no easy alternative.

BTW you may find this recent post about triggers by nothingmuch interesting.

/I3az/

draegtun
Check out the Moose::Manual::Attributes on triggers - http://search.cpan.org/~drolsky/Moose-0.88/lib/Moose/Manual/Attributes.pod#Triggers
Drew Stephens
+3  A: 

If dealing with the hash directly is causing you concern, you could specify an alternate writer and then use that from within your own appropriately named 'public' writer.

package Foo;
use Moose;

has 'bar' => (
   isa => 'Str',
   reader => 'get_bar',
   writer => '_set_bar',
);

sub set_bar {
   my $self = shift;
   my @args = @_;
   # play with args;
   return $self->_set_bar(@args);
}

This, or triggers, would strike me as being a good approach depending on when and how you need to be manipulating the arguments.

(disclaimer: untested code written from memory, browsing SO on a netbook with flaky edge access)

jsoverson
This is a nice clean solution though it won't work with constructors.
mikegrb
@mikegrb: I'm not sure what exactly you want to do with constructors, but you can specify where a value is assigned via a constructor using the `init_arg` attribute modifier, and/or you could do some checks in the `BUILDARGS` method.
Ether
+5  A: 

I'm not sure what kind of modification you need, but you might be able to achieve what you need by using type coercion:

package Foo;
use Moose;

use Moose::Util::TypeConstraints;

subtype 'ModStr' 
    => as 'Str'
    => where { /^modified: /};

coerce 'ModStr'
    => from 'Str'
    => via { "modified: $_" };

has 'bar' => ( 
    isa => 'ModStr', 
    is  => 'rw', 
    coerce => 1,
);

If you use this approach, not all values will be modified. Anything that passes validation as a ModStr will be used directly:

my $f = Foo->new();
$f->bar('modified: bar');  # Set without modification

This weakness could be OK or it could make this approach unusable. In the right circumstances, it might even be an advantage.

daotoad
+4  A: 

You can use the method modifier 'around'. Something like this:

has 'bar' => (
    isa    => 'Str',
    reader => 'get_bar',
    writer => 'set_bar'
);

around 'set_bar' => sub {
    my ($next, $self, $bar) = @_;
    $self->$next( "Modified: $bar" );
};

And yes, working directly with the hash values is considered bad practice.

Also, please don't assume that the option I presented is necessarily the right one. Using subtypes and coercion is going to be the right solution most of the time - if you think about your parameter in terms of a type that can possibly be reused throughout your application will lead to much better design that the kind of arbitrary modifications that can be done using 'around'. See answer from @daotoad.

megamic
For this to work you do need to declare a writer when defining the attribute. For eg, has 'bar' => (is=>'rw', isa=>'Str', writer=>'set_bar')
draegtun
re: hash values "bad practise": Its fine in triggers and examples i've seen (on Moose mailing list) do reaffirm this.
draegtun
draegtun: unless Moose has changed it the default writer is the attribute name. You can put around on that if you don't want to specify the writer.
Jeremy Wall
@Jeremy Wall: Without declaring a separate writer/reader then the "around" will affect the default getter as well as the default setter of an attribute so it will cause problems (throws an compilation error here for me).
draegtun