On Oct 11, 2011, at 10:30 AM, Peter Gordon wrote:
> I am trying to find a decent design pattern for Moose validation of
> user input. All the Moose examples I have found either assume that
> the data is correct or else dies.
>
> Example:
> package Address ;
> use Moose;
> use Moose::Util::TypeConstraints;
>
> subtype 'Email',
> as 'Str',
> where { $_ =~ m!@! },
> message { "The email you provided, $_, was not a valid email" };
>
> package Person ;
> use Moose ;
> has 'email' => (is => 'rw', isa => 'Email', required => 1) ;
>
> package main ;
>
> my $f = Person->new(email => 'xa.com') ;
>
> The above pattern is much like the examples in the manual.
>
>> From an OOP perspective, it seems to me that the validation performed
> by the subtype should be part of the class and not something that is
> just floating around. There is also no reasonable way to trap the
> error, and report back to the user that there was an error. And I am
> not a fan of try/catch.
>
> I have come up with the following design pattern to allow validation.
> The idea is to create a class, Email, which accepts any data. If the
> data is good, error is set to 0. If the error is set to 1, it also
> changes the package name so that if it is passed onwards the program
> dies.
>
> package EmailRole ;
> use Moose::Role ;
>
> has 'email' => ( is => 'rw', isa => 'Str',required => 1) ;
> has 'error' => ( is => 'rw', isa => 'Str', default => 0) ;
>
> sub BUILD {
> my $my = shift ;
> if ($my->email !~ m!@!) {
> $my->error(1) ;
> bless $my, 'EmailError';
> }
> return $my ;
> }
> no Moose ;
>
> package Email ;
> use Moose ;
> with 'EmailRole' ;
>
> package EmailError ;
> use Moose ;
> with 'EmailRole';
>
> no Moose ;
>
> package Person ;
> use Moose ;
> has 'email' => ( is => 'ro', isa => 'Email' ) ;
>
> package main ;
> my $emailGood = Email->new(email => '[email protected]') ;
> my $personGood = Person->new(email => $emailGood) ;
>
> my $emailBad = Email->new(email => 'xxx') ;
> print "Bad Person" if $emailBad->error ;
> # And if we insist on proceeding, the following line will fail.
> my $personBad = Person->new(email => $emailBad) ;
>
> What I would like is some feedback
> a) what are the pitfalls of this design.
> b) are there any better designs?
>
Peter,
Validation is different then type checking, which is why you are running into
this issue. Putting the validation in the type allows a certain kind of re-use
that goes beyond just using it in a class. Remember that Moose types are
themselves objects which you can call methods on, etc.
I recommend looking at something like Data::Manager
(https://www.metacpan.org/module/Data::Manager) or the lower level
Data::Verifier (https://www.metacpan.org/module/Data::Verifier) which will
allow you to use Moose types to test things and capture errors effectively. It
also is a great illustration of using Moose types in a totally different
context.
- Stevan