From: Dave Rolsky Date: Sun, 8 Feb 2009 19:08:33 +0000 (+0000) Subject: An editorial pass through basics recipe 4 X-Git-Tag: 0.69~41 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=4a6b74bd37ead12cc40ac2f42f95062988e94fdf;p=gitmo%2FMoose.git An editorial pass through basics recipe 4 --- diff --git a/lib/Moose/Cookbook/Basics/Recipe4.pod b/lib/Moose/Cookbook/Basics/Recipe4.pod index 857b9aa..480dfc2 100644 --- a/lib/Moose/Cookbook/Basics/Recipe4.pod +++ b/lib/Moose/Cookbook/Basics/Recipe4.pod @@ -43,8 +43,8 @@ Moose::Cookbook::Basics::Recipe4 - Subtypes, and modeling a simple B cl sub BUILD { my ( $self, $params ) = @_; - if ( $params->{employees} ) { - foreach my $employee ( @{ $params->{employees} } ) { + if ( @{ $self->employees } ) { + foreach my $employee ( @{ $self->employees } ) { $employee->company($self); } } @@ -52,7 +52,7 @@ Moose::Cookbook::Basics::Recipe4 - Subtypes, and modeling a simple B cl after 'employees' => sub { my ( $self, $employees ) = @_; - if ( defined $employees ) { + if ($employees) { foreach my $employee ( @{$employees} ) { $employee->company($self); } @@ -85,8 +85,8 @@ Moose::Cookbook::Basics::Recipe4 - Subtypes, and modeling a simple B cl extends 'Person'; - has 'title' => ( is => 'rw', isa => 'Str', required => 1 ); - has 'company' => ( is => 'rw', isa => 'Company', weak_ref => 1 ); + has 'title' => ( is => 'rw', isa => 'Str', required => 1 ); + has 'employer' => ( is => 'rw', isa => 'Company', weak_ref => 1 ); override 'full_name' => sub { my $self = shift; @@ -95,46 +95,40 @@ Moose::Cookbook::Basics::Recipe4 - Subtypes, and modeling a simple B cl =head1 DESCRIPTION -In this recipe we introduce the C keyword, and show -how it can be useful for specifying type constraints -without building 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 -introduce another attribute option. +This recipe introduces the C sugar function from +L. The C function lets you +declaratively create type constraints without building an entire +class. -Let's first look at the C feature. In the B
class we have -defined two subtypes. The first C uses the L module, which -provides two hashes which can be used to perform existential checks for state -names and their two letter state codes. It is a very simple and very useful -module, and perfect for use in a C constraint. +In the recipe we also make use of L and L +to build constraints, showing how how constraints can make use of +existing CPAN tools for data validation. - my $STATES = Locale::US->new; - subtype 'USState' - => as Str - => where { - ( exists $STATES->{code2state}{ uc($_) } - || exists $STATES->{state2code}{ uc($_) } ); - }; +Finally, we introduce the C attribute parameter. + +The the C
class we define two subtypes. The first uses the +L module to check the validaity of a state. It accepts +either a state abbreviation of full name. + +A state will be passed in as a string, so we make our C type +a subtype of Moose's builtin C type. This is done using the C +sugar. The actual constraint is defined using C. This function +accepts a single subroutine reference. That subroutine will be called +with the value to be checked in C<$_> (1). It is expected to return a +true or false value indicating whether the value is valid for the +type. -Because we know that states will be passed to us as strings, we -can make C a subtype of the built-in type constraint -C. This will ensure that anything which is a C will -also pass as a C. Next, we create a constraint specializer -using the C keyword. The value being checked against in -the C clause can be found in the C<$_> variable (1). Our -constraint specializer will then check whether the given string -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: +We can now use the C type just like Moose's builtin types: has 'state' => ( is => 'rw', isa => 'USState' ); -The C accessor will now check all values against the -C constraint, thereby only allowing valid state names or -state codes to be stored in the C slot. +When the C attribute is set, the value is checked against the +C constraint. If the value is not valid, an exception will be +thrown. -The next C does pretty much the same thing using the L -module, and is used as the constraint for the C slot. +The next C, C, uses +L. L includes a regex for validating +US zip codes. We use this constraint for the C attribute. subtype 'USZipCode' => as Value @@ -142,73 +136,91 @@ module, and is used as the constraint for the C slot. /^$RE{zip}{US}{-extended => 'allow'}$/; }; -Using subtypes can save a lot of unnecessary abstraction by not requiring you to -create many small classes for these relatively simple values. They also allow -you to reuse the same constraints in a number of classes (thereby avoiding -duplication), since all type constraints are stored in a global registry and -always accessible to C. +Using a subtype instead of requiring a class for each type greatly +simplifies the code. We don't really need a class for these types, as +they're just strings, but we do want to ensure that they're valid. + +The type constraints we created are reusable. Type constraints are +stored by name in a global registry. This means that we can refer to +them in other classes. Because the registry is global, we do recommend +that you use some sort of pseudo-namespacing in real applications, +like C. + +These two subtypes allow us to define a simple C
class. -With these two subtypes and some attributes, we have defined -as much as we need for a basic B
class. Next, we define -a basic B class, which itself has an address. As we saw in -earlier recipes, we can use the C
type constraint that -Moose automatically created for us: +Then we define our C class, which has an address. As we saw +in earlier recipes, Moose automatically creates a type constraint for +each our classes, so we can use that for the C class's +C
attribute: has 'address' => ( is => 'rw', isa => 'Address' ); -A company also needs a name, so we define that as well: +A company also needs a name: has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); -Here we introduce another attribute option, the C option. -This option tells Moose that C is a required parameter in -the B constructor, and that the C accessor cannot -accept an undefined value for the slot. The result is that C -will always have a value. +This introduces a new attribute parameter, C. If an +attribute is required, then it must be passed to the class's +constructor, or an exception will be thrown. It's important to +understand that a C attribute can still be false or +C, if its type constraint allows that. -The next attribute option is not actually new, but a new variant -of options we have already introduced: +The next attribute, C, uses a I type +constraint: has 'employees' => ( is => 'rw', isa => 'ArrayRef[Employee]' ); -Here, we are passing a more complex string to the C option, we -are passing a container type constraint. Container type constraints -can either be C or C with a contained type given -inside the square brackets. This basically checks that all the values -in the ARRAY ref are instances of the B class. - -This will ensure that our employees will all be of the correct type. However, -the B object (which we will see in a moment) also maintains a -reference to its associated B. In order to maintain this relationship -(and preserve the referential integrity of our objects), we need to perform some -processing of the employees over and above that of the type constraint check. -This is accomplished in two places. First we need to be sure that any employees -array passed to the constructor is properly initialized. For this we can use the -C method (2): +This constraint says that C must be an array reference +where each element of the array is an C object. It's worth +noting that an I array reference also satisfies this +constraint. + +Parameterizable type constraints (or "container types), such as +C, can be made more specific with a type parameter. In +fact, we can arbitrarily nest these types, producing something like +C. However, you can also just use the type by +itself, so C is legal. (2) + +If you jump down to the definition of the C class, you will +see that it has an C attribute. + +When we set the C for a C we want to make sure +that each of these employee objects refers back to the right +C in its C attribute. + +To do that, we need to hook into object construction. Moose lets us do +this by writing a C method in our class. When your class +defined a C method, it will be called immediately after an +object construction, but before the object is returned to the caller +(3). + +The C class uses the C method to ensure that each +employee of a company has the proper C object in its +C attribute: sub BUILD { my ( $self, $params ) = @_; - if ( $params->{employees} ) { - foreach my $employee ( @{ $params->{employees} } ) { + if ( $self->employees ) { + foreach my $employee ( @{ $self->employees } ) { $employee->company($self); } } } -The C method will be executed after the initial type constraint -check, so we can simply perform a basic existential check on the C -parameter here, and assume that if it does exist, it is both an ARRAY ref -and contains I instances of B. +The C method is executed after type constraints are checked, so +it is safe to assume that C<< $self->employees >> will return an array +reference, and that the elements of that array will be C +objects. + +We also want to make sure that whenever the C attribute for +a C is changed, we also update the C for each +employee. -The next aspect we need to address is the C read/write -accessor (see the C attribute declaration above). This -accessor will correctly check the type constraint, but we need to extend it -with some additional processing. For this we use an C method modifier, -like so: +To do this we can use an C modifier: after 'employees' => sub { my ( $self, $employees ) = @_; - if ( defined $employees ) { + if ($employees) { foreach my $employee ( @{$employees} ) { $employee->company($self); } @@ -219,47 +231,38 @@ Again, as with the C method, we know that the type constraint check has already happened, so we can just check for definedness on the C<$employees> argument. -At this point, our B class is complete. Next comes our B -class and its subclass, the previously mentioned B class. +The B class does have demonstrate anything new. It has several +C attributes. It also has a C method, which we +first used in L. -The B class should be obvious to you at this point. It has a few -C attributes, and the C slot has an additional -C method (which we saw in the previous recipe with the -B class). - -Next, the B class, which should also be pretty obvious at this -point. It requires a C, and maintains a weakened reference to a -B<Company> instance. The only new item, which we have seen before in -examples, but never in the recipe itself, is the C<override> method -modifier: +The only new feature in the C<Employee> class is the C<override> +method modifier: override 'full_name' => sub { my $self = shift; super() . ', ' . $self->title; }; -This just tells Moose that I am intentionally overriding the superclass -C<full_name> method here, and adding the value of the C<title> slot at -the end of the employee's full name. - -And that's about it. +This is just a sugary alternative to Perl's built in C<SUPER::> +feature. However, there is one difference. You cannot pass any +arguments to C<super>. Instead, Moose ismply passes the same +parameters that were passed to the method. -Once again, as with all the other recipes, you can go about using -these classes like any other Perl 5 class. A more detailed example of -usage can be found in F<t/000_recipes/004_recipe.t>. +A more detailed example of usage can be found in +F<t/000_recipes/004_recipe.t>. =head1 CONCLUSION -This recipe was intentionally longer and more complex to illustrate both -how easily Moose classes can interact (using class type constraints, etc.) -and the sheer density of information and behaviors which Moose can pack -into a relatively small amount of typing. Ponder for a moment how much -more code a non-Moose plain old Perl 5 version of this recipe would have -been (including all the type constraint checks, weak references, and so on). +This recipe was intentionally longer and more complex. It illustrates +how Moose classes can be used together with type constraints, as well +as the density of information that you can get out of a small amount +of typing when using Moose. + +This recipe also introduced the C<subtype> function, the C<required> +attribute, and the C<override> method modifier. -And of course, this recipe also introduced the C<subtype> keyword, and -its usefulness within the Moose toolkit. In the next recipe we will -focus more on subtypes, and introduce the idea of type coercion as well. +We will revisit type constraints in future recipes, and cover type +coercion as well. =head1 FOOTNOTES @@ -268,15 +271,20 @@ focus more on subtypes, and introduce the idea of type coercion as well. =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. +the C<where> block, so it can be accessed as C<$_[0]>. =item (2) -The C<BUILD> method is called by C<Moose::Object::BUILDALL>, which is -called by C<Moose::Object::new>. C<BUILDALL> will climb the object -inheritance graph and call the appropriate C<BUILD> methods in the -correct order. +Note that C<ArrayRef[]> will not work. Moose will not parse this as a +container type, and instead you will have a new type named +"ArrayRef[]", which doesn't make any sense. + +=item (3) + +The C<BUILD> method is actually called by C<< Moose::Object->BUILDALL +>>, which is called by C<< Moose::Object->new >>. The C<BUILDALL> +method climbs the object inheritance graph and calls any C<BUILD> +methods it finds in the correct order. =back