subtype USState
=> as Str
=> where {
- (exists $STATES->{code2state}{uc($_)} || exists $STATES->{state2code}{uc($_)})
+ (exists $STATES->{code2state}{uc($_)} ||
+ exists $STATES->{state2code}{uc($_)})
};
subtype USZipCode
has 'first_name' => (is => 'rw', isa => 'Str', required => 1);
has 'last_name' => (is => 'rw', isa => 'Str', required => 1);
- has 'middle_initial' => (is => 'rw', isa => 'Str', predicate => 'has_middle_initial');
+ has 'middle_initial' => (is => 'rw', isa => 'Str',
+ predicate => 'has_middle_initial');
has 'address' => (is => 'rw', isa => 'Address');
sub full_name {
my $self = shift;
return $self->first_name .
- ($self->has_middle_initial ? ' ' . $self->middle_initial . '. ' : ' ') .
+ ($self->has_middle_initial ?
+ ' ' . $self->middle_initial . '. '
+ :
+ ' ') .
$self->last_name;
}
=head1 DESCRIPTION
+In this recipe we introduce the C<subtype> keyword, and show
+how that can be useful for specifying specific type constraints
+without having to build an entire class to represent them. We
+will also show how this feature can be used to leverage the
+usefulness of CPAN modules. In addition to this, we will also
+introduce another attribute option as well.
+
+Lets first get into the C<subtype> features. In the B<Address>
+class we have defined two subtypes. The first C<subtype> uses
+the L<Locale::US> module, which provides two hashes which can be
+used to do existence checks for state names and their two letter
+state codes. It is a very simple, and very useful module, and
+perfect to use in a C<subtype> constraint.
+
+ my $STATES = Locale::US->new;
+ subtype USState
+ => as Str
+ => where {
+ (exists $STATES->{code2state}{uc($_)} ||
+ exists $STATES->{state2code}{uc($_)})
+ };
+
+Because we know that states will be passed to us as strings, we
+can make C<USState> a subtype of the built-in type constraint
+C<Str>. This will assure that anything which is a C<USState> will
+also pass as a C<Str>. Next, we create a constraint specializer
+using the C<where> keyword. The value being checked against in
+the C<where> clause can be found in the C<$_> variable (1). Our
+constraint specializer will then look to see if the string given
+is either a state name or a state code. If the string meets this
+criteria, then the constraint will pass, otherwise it will fail.
+We can now use this as we would any built-in constraint, like so:
+
+ has 'state' => (is => 'rw', isa => 'USState');
+
+The C<state> accessor will now check all values against the
+C<USState> constraint, thereby only allowing valid state names or
+state codes to be stored in the C<state> slot.
+
+The next C<subtype>, does pretty much the same thing using the
+L<Regexp::Common> module, and constrainting the C<zip_code> slot.
+
+ subtype USZipCode
+ => as Value
+ => where {
+ /^$RE{zip}{US}{-extended => 'allow'}$/
+ };
+
+Using subtypes can save a lot of un-needed abstraction by not
+requiring you to create many small classes for these relatively
+simple values. It also allows you to define these constraints
+and share them among many different classes (avoiding unneeded
+duplication) because type constraints are stored by string in a
+global registry and always accessible to C<has>.
+
+With these two subtypes and some attributes, we pretty much define
+as much as we need for a basic B<Address> class. Next we define
+a basic B<Company> class, which itself has an address. As we saw in
+earlier recipes, we can use the C<Address> type constraint that
+Moose automatically created for us.
+
+ has 'address' => (is => 'rw', isa => 'Address');
+
+A company also needs a name, so we define that too.
+
+ has 'name' => (is => 'rw', isa => 'Str', required => 1);
+
+Here we introduce another attribute option, the C<required> option.
+This option tells Moose that C<name> is a required parameter in
+the B<Company> constructor, and that the C<name> accessor cannot
+accept an undefined value for the slot. The result is that C<name>
+should always have a value.
+
+=head1 FOOTNOTES
+
+=over 4
+
+=item (1)
+
+The value being checked is also passed as the first argument to
+the C<where> block as well, so it can also be accessed as C<$_[0]>
+as well.
+
+=back
+
=head1 AUTHOR
Stevan Little E<lt>stevan@iinteractive.comE<gt>