develooper Front page | perl.moose | Postings from February 2009

Cleaning attribute inputs

Thread Next
Chad Davis
February 16, 2009 10:09
Cleaning attribute inputs
Message ID:
I'm re-posting this to the Moose list. Hopefully some of the Moose
experts can offer their insight.

The following is about cleaning-up attributes, before allowing them to
be set, e.g. HTML encoding or removing invalid characters from a
string input. The question is which Moose approach is best to
accomplish this: before, after, around, trigger, BUILD, or coerce.

>> At a higher level, I wasn't sure when it's more appropriate to use
>> before/after/around vs. triggers.
>> E.g. before/after/around are not called for attributes set in the
>> constructor. In that case, trigger does work, though. However, for doing
>> cleanup of attribute values before allowing them to be set (e.g. HTML
>> encoding a string), trigger does not seem to get access to @_ So, if I use
>> trigger to recall $self->myattribute(), I'll have infinite recursion. In
>> the
>> end, I found coercion to be the best (only?) way. This kind of
>> attribute-cleanup wrapper might be something for the cookbook.
> I'm lost here. Triggers _are_ passed the value of the attribute set in the
> constructor, or at least that's what the docs say ;)
> Triggers don't have access to _all_ of the arguments passed to the
> constructor, though. For that, use BUILD or BUILDARGS.

I admit that trigger does get @_, like you said, but if I call the
accessor from the trigger I get infinite recursion:

package Cleaner;
use Moose;
has 'myattr' => (
   is => 'rw',
   isa => 'Str',
#     trigger => \&triggercleaner,
sub triggercleaner {
   my ($self, $arg) = @_;
   $arg =~ s/\s//g;
   $self->myattr($arg);   # <--- infinite recursion

'Around' would be the ideal solution:

around 'myattr' => \&aroundcleaner;
sub aroundcleaner {
   my ($orig, $self, $val) = @_;
   return $self->$orig() unless $val;
   $val =~ s/\s//g;
   return $self->$orig($val);

Except that these two bits of code produce different results,
depending on where myattr is set. So I cannot enforce that the value
is always clean.

my $x = new Cleaner(myattr=>"around not called from constructor");
print "Spaces still there:", $x->myattr, "\n";

# But then calling this does call aroundcleaner()
print "Spaces removed ", $x->myattr, "\n";

So, I need a BUILD, because the constructor is a special case:

sub BUILD {
   my ($self) = @_;
   # either this:
   aroundcleaner(\&myattr, $self, $self->myattr);
   # or this will work:

But that needs to be done for any further attributes that need
cleaning. If this were classic OO, it would just be:

sub myattr {
   my ($self, $val) = @_;
   return $self->{'myattr'} unless defined $val;
   $val =~ s/\s//g;
   return $self->{'myattr'} = $val;

Which is why I'm slightly perplexed, because this seems to be the one
use case where Moose requires more code than classic OO.

I could just break the interface and do: $self->{'myattr'} = $val
inside the trigger, but none of us wants that.

I finally settled on coercion to do something like:

use Moose::Util::TypeConstraints;
subtype 'CleanStr' => as 'Str';
coerce 'CleanStr'
   => from 'Str'
   => via { $_ =~ s/\s//g; $_ };
has 'myattr' => (
   is => 'rw',
   isa => 'CleanStr',
   coerce => 1,

But this also seems like a round-about solution, since I all I want is
to insert somewhere: s/\s//g

Is someone still following that might also have a good solution for
preventing unwanted values from getting into an attribute?


Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About