cleanup
Stevan Little [Tue, 28 Mar 2006 17:42:28 +0000 (17:42 +0000)]
lib/Moose/Cookbook/Recipe4.pod

index b4f38ed..26265a5 100644 (file)
@@ -20,7 +20,8 @@ Moose::Cookbook::Recipe4
   subtype USState 
       => as Str
       => where {
-          (exists $STATES->{code2state}{uc($_)} || exists $STATES->{state2code}{uc($_)})
+          (exists $STATES->{code2state}{uc($_)} || 
+           exists $STATES->{state2code}{uc($_)})
       };
       
   subtype USZipCode 
@@ -63,13 +64,17 @@ Moose::Cookbook::Recipe4
   
   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;
   }
     
@@ -90,6 +95,91 @@ Moose::Cookbook::Recipe4
     
 =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>