Revised Basics Recipe 1 a fair bit ...
Dave Rolsky [Tue, 5 Aug 2008 17:19:41 +0000 (17:19 +0000)]
Got rid of direct hashref access, since that is just confusing to see
in a first example (it's an example of what _not_ to do).

A lot of stylistic changes to make the text shorter and more
consistent (use first person plural everywhere).

lib/Moose/Cookbook/Basics/Recipe1.pod
t/000_recipes/basics/001_point.t

index bfcec7e..28f88de 100644 (file)
@@ -9,180 +9,170 @@ Moose::Cookbook::Basics::Recipe1 - The (always classic) B<Point> example.
 
   package Point;
   use Moose;
-       
-  has 'x' => (isa => 'Int', is => 'ro');
-  has 'y' => (isa => 'Int', is => 'rw');
-  
+
+  has 'x' => (isa => 'Int', is => 'rw', required => 1);
+  has 'y' => (isa => 'Int', is => 'rw', required => 1);
+
   sub clear {
       my $self = shift;
-      $self->{x} = 0;
-      $self->y(0);    
+      $self->x(0);
+      $self->y(0);
   }
-  
+
   package Point3D;
   use Moose;
-  
+
   extends 'Point';
-  
-  has 'z' => (isa => 'Int');
-  
+
+  has 'z' => (isa => 'Int', is => 'rw', required => 1);
+
   after 'clear' => sub {
       my $self = shift;
-      $self->{z} = 0;
+      $self->z(0);
   };
 
 =head1 DESCRIPTION
 
-This is the classic Point example. This one in particular I took 
-from the Perl 6 Apocalypse 12 document, but it is similar to the 
-example found in the classic K&R C book as well, and many other 
-places. And now, onto the code:
-
-As with all Perl 5 classes, a Moose class is defined in a package. 
-Moose now handles turning on C<strict> and C<warnings> for you, so 
-all you need to do is say C<use Moose>, and no kittens will die.
-
-By loading Moose, we are enabling the loading of the Moose
-"environment" into our package. This means that we import some
-functions which serve as Moose "keywords". These aren't anything
-fancy, just plain old exported functions.
+This is the classic Point example. It is taken directly from the Perl
+6 Apocalypse 12 document, and is similar to the example found in the
+classic K&R C book as well.
 
-Another important thing happens at this stage as well. Moose will 
-automatically set your package's superclass to be L<Moose::Object>.
-The reason we do this, is so that we can be sure that your class
-will inherit from L<Moose::Object> and get the benefits that
-provides (such as a constructor; see L<Moose::Object> for details).
-However, you don't actually I<have> to inherit from L<Moose::Object>
-if you don't want to. All Moose features will still be accessible to
-you.
+As with all Perl 5 classes, a Moose class is defined in a package.
+Moose handles turning on C<strict> and C<warnings> for us, so all we
+need to do is say C<use Moose>, and no kittens will die.
 
-Now, onto the keywords. The first one we see here is C<has>, which 
-defines an instance attribute in your class:
+When Moose is loaded, it exports a set of sugar functions into our
+package. This means that we import some functions which serve as Moose
+"keywords". These aren't real language keywords, they're just Perl
+functions exported into our package.
 
-  has 'x' => (isa => 'Int', is => 'ro');
+Moose automatically makes our package a subclass of L<Moose::Object>.
+The L<Moose::Object> class provides us with a constructor that
+respects our attributes, as well other features. See L<Moose::Object>
+for details.
 
-This will create an attribute named C<x>, which will expect the 
-value stored in the attribute to pass the type constraint C<Int> (1), 
-and the accessor generated for this attribute will be read-only 
-(abbreviated as C<ro>).
+Now, onto the keywords. The first one we see here is C<has>, which
+defines an instance attribute in our class:
 
-The next C<has> line is very similar, with only one difference:
+  has 'x' => (isa => 'Int', is => 'rw', required => 1);
 
-  has 'y' => (isa => 'Int', is => 'rw');
+This will create an attribute named C<x>. The C<isa> parameter says
+that we expect the value stored in this attribute to pass the type
+constraint for C<Int> (1). The accessor generated for this attribute
+will be read-write.
 
-A read/write (abbreviated as C<rw>) accessor will be generated for
-the C<y> attribute.
+The C<< requires => 1 >> parameter means that this attribute must be
+provided when a new object is created. A point object without
+coordinates doesn't make much sense, so we don't allow it.
 
-At this point the attributes have been defined, and it is time to 
-define our methods. In Moose, as with regular Perl 5 OO, a method 
-is just a subroutine defined within the package. So here we create 
-the C<clear> method.
+We have defined our attributes; next we define our methods. In Moose,
+as with regular Perl 5 OO, a method is just a subroutine defined
+within the package:
 
   sub clear {
       my $self = shift;
-      $self->{x} = 0;
-      $self->y(0);    
+      $self->x(0);
+      $self->y(0);
   }
 
-It is pretty standard, the only thing to note is that we are directly 
-accessing the C<x> slot in the instance L<(2)>. This is because the 
-value was created with a read-only accessor. This also shows that Moose 
-objects are not anything out of the ordinary, but just regular old 
-blessed HASH references. This means they are very compatible with 
-other Perl 5 (non-Moose) classes as well. 
+That concludes the B<Point> class.
 
-The next part of the code to review is the B<Point> subclass, 
-B<Point3D>. The first item you might notice is that we do not use 
-the standard C<use base> declaration here. Instead we use the Moose 
-keyword C<extends> like so:
+Next we have a subclass of B<Point>, B<Point3D>. To declare our
+superclass, we use the Moose keyword C<extends>:
 
   extends 'Point';
 
-This keyword will function very much like C<use base> does in that 
-it will make an attempt to load your class if it has not already been 
-loaded. However, it differs on one important point. The C<extends> 
-keyword will overwrite any previous values in your package's C<@ISA>, 
-where C<use base> will C<push> values onto the package's C<@ISA>. It 
-is my opinion that the behavior of C<extends> is more intuitive in 
-that it is more explicit about defining the superclass relationship.
+The C<extends> keyword works much like C<use base>. First, it will
+attempt to load your class if needed. However, unlike C<base>, the
+C<extends> keyword will I<overwrite> any previous values in your
+package's C<@ISA>, where C<use base> will C<push> values onto the
+package's C<@ISA>.
 
-A small digression here: both Moose and C<extends> support multiple 
-inheritance. You simply pass all the superclasses to C<extends>, 
-like so:
+It is my opinion that the behavior of C<extends> is more intuitive.
+(2).
 
-  extends 'Foo', 'Bar', 'Baz';
-  
-Now, back to our B<Point3D> class. The next thing we do is to create 
-a new attribute for B<Point3D> called C<z>.
+Next we create a new attribute for B<Point3D> called C<z>.
 
-  has 'z' => (isa => 'Int');
+  has 'z' => (isa => 'Int', is => 'rw', required => 1);
 
-As with B<Point>'s C<x> and C<y> attributes, this attribute has a 
-type constraint of C<Int>, but it differs in that it does B<not> 
-ask for any autogenerated accessors. The result being (aside from 
-broken object encapsulation) that C<z> is a private attribute.
+This attribute is just like B<Point>'s C<x> and C<y> attributes.
 
-Next comes another Moose feature which we call method "modifiers" 
-(or method "advice" for the AOP inclined). The modifier used here 
-is the C<after> modifier, and looks like this:
+The C<after> keyword demonstrates a Moose feature called "method
+modifiers" (or "advice" for the AOP inclined):
 
   after 'clear' => sub {
       my $self = shift;
-      $self->{z} = 0;
+      $self->z(0);
   };
 
-This modifier tells Moose to install a C<clear> method for 
-B<Point3D> that will first run the C<clear> method for the 
-superclass (in this case C<Point::clear>), and then run this 
-method I<after> it (passing in the same arguments as the original 
-method). 
+When C<clear> is called on a B<Point3D> object, our modifier method
+gets called as well. Unsurprisingly, the modifier is called I<after>
+the real method.
+
+In this case, the real C<clear> method is inherited from B<Point>. Our
+modifier method receives the same arguments as those passed to the
+modified method (just C<$self> here).
 
-Now, of course using the C<after> modifier is not the only way to 
-accomplish this. I mean, after all, this B<is> Perl right? You 
-would get the same results with this code:
+Of course, using the C<after> modifier is not the only way to
+accomplish this. This B<is> Perl, right? You can get the same results
+with this code:
 
   sub clear {
       my $self = shift;
       $self->SUPER::clear();
-      $self->{z} = 0;
+      $self->z(0);
   }
 
-You could also use another Moose method modifier, C<override> here, 
-and get the same results again. Here is how that would look:
+You could also use another Moose method modifier, C<override>:
 
   override 'clear' => sub {
       my $self = shift;
       super();
-      $self->{z} = 0;
+      $self->z(0);
   };
-  
-The C<override> modifier allows you to use the C<super> keyword 
-within it to dispatch to the superclass's method in a very Ruby-ish 
-style.
 
-Now, of course, what use is a class if you can't instantiate objects 
-with it? Since B<Point> inherits from L<Moose::Object>, it will also
-inherit the default L<Moose::Object> constructor: C<new>. Here 
-are two examples of how that is used:
+The C<override> modifier allows you to use the C<super> keyword to
+dispatch to the superclass's method in a very Ruby-ish style.
 
-  my $point = Point->new(x => 1, y => 2);   
+The choice of whether to use a method modifier, and which one to use,
+is often a question of style as much as functionality.
+
+Since B<Point> inherits from L<Moose::Object>, it will also inherit
+the default L<Moose::Object> constructor:
+
+  my $point   = Point  ->new(x => 1, y => 2);
   my $point3d = Point3D->new(x => 1, y => 2, z => 3);
 
-As you can see, C<new> accepts named argument pairs for any of the 
-attributes. It does not I<require> that you pass in the all the 
-attributes, and it will politely ignore any named arguments it does 
-not recognize. 
+The C<new> constructor accepts a named argument pair for each
+attribute defined by the class. In this particular example, the
+attributes are required, and calling C<new> without them will throw an
+error.
+
+From here on, we can use C<$point> and C<$point3d> just as you would
+any other Perl 5 object. For a more detailed example of what can be
+done, you can refer to the F<t/000_recipes/basic/001_point.t> test
+file.
+
+=head2 Moose Objects are Just Hashrefs
+
+While this all may appear rather magical, it's important to realize
+that Moose objects are just hash references under the hood (3). For
+example, you could pass C<$self> to C<Data::Dumper> and you'd get
+exactly what you'd expect.
 
-From here on, you can use C<$point> and C<$point3d> just as you would 
-any other Perl 5 object. For a more detailed example of what can be 
-done, you can refer to the F<t/000_recipes/001_recipe.t> test file.
+You could even poke around inside the object's data structure, but
+that is strongly discouraged.
+
+The fact that Moose objects are hashrefs means it is easy to use Moose
+to extend non-Moose classes, as long as they too are hash
+references. If you want to extend a non-hashref class, check out
+C<MooseX::InsideOut>.
 
 =head1 CONCLUSION
 
-I hope this recipe has given you some explanation of how to use 
-Moose to build your Perl 5 classes. The next recipe will build upon 
-the basics shown here with more complex attributes and methods. 
-Please read on :)
+This recipe demonstrates some basic Moose concepts. The next recipe
+will build upon the basics shown here with more complex attributes and
+methods. Please read on :)
 
 =head1 FOOTNOTES
 
@@ -190,21 +180,22 @@ Please read on :)
 
 =item (1)
 
-Several default type constraints are provided by Moose, of which 
-C<Int> is one. For more information on the builtin type constraints 
-and the type constraint system in general, see the 
-L<Moose::Util::TypeConstraints> documentation.
+Moose provides a number of builtin type constraints are provided by,
+of which C<Int> is one. For more information on the type constraint
+system, see L<Moose::Util::TypeConstraints>.
 
 =item (2)
 
+The C<extends> keyword support multiple inheritance. Simply pass all
+of your superclasses to C<extends> as a list:
+
+  extends 'Foo', 'Bar', 'Baz';
+
+=item (3)
+
 Moose supports using instance structures other than blessed hash
-references (such as in a glob reference -- see
-L<MooseX::GlobRef::Object>). If you want your Moose classes to
-be interchangeable, it is advisable to avoid direct instance
-access, like that shown above. Moose does let you get and set
-attributes directly without exposing the instance structure, but
-that's an advanced topic (intrepid readers should refer to the
-L<Moose::Meta::Attribute documentation>).
+references (such as in a glob reference - see
+L<MooseX::GlobRef::Object>).
 
 =back
 
@@ -234,4 +225,4 @@ L<http://www.iinteractive.com>
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself.
 
-=cut
\ No newline at end of file
+=cut
index cef1850..6c71552 100644 (file)
@@ -10,12 +10,12 @@ use Test::Exception;
     package Point;
     use Moose;
 
-    has 'x' => ( isa => 'Int', is => 'ro' );
-    has 'y' => ( isa => 'Int', is => 'rw' );
+    has 'x' => (isa => 'Int', is => 'rw', required => 1);
+    has 'y' => (isa => 'Int', is => 'rw', required => 1);
 
     sub clear {
         my $self = shift;
-        $self->{x} = 0;
+        $self->x(0);
         $self->y(0);
     }
 
@@ -28,11 +28,11 @@ use Test::Exception;
 
     extends 'Point';
 
-    has 'z' => ( isa => 'Int' );
+    has 'z' => (isa => 'Int', is => 'rw', required => 1);
 
     after 'clear' => sub {
         my $self = shift;
-        $self->{z} = 0;
+        $self->z(0);
     };
 
     __PACKAGE__->meta->make_immutable( debug => 0 );
@@ -53,9 +53,8 @@ dies_ok {
 } '... cannot assign a non-Int to y';
 
 dies_ok {
-    $point->x(1000);
-} '... cannot assign to a read-only method';
-is($point->x, 1, '... got the right (un-changed) value for x');
+    Point->new();
+} '... must provide required attributes to new';
 
 $point->clear();
 
@@ -87,15 +86,11 @@ is($point3d->x, 10, '... got the right value for x');
 is($point3d->y, 15, '... got the right value for y');
 is($point3d->{'z'}, 3, '... got the right value for z');
 
-dies_ok {
-       $point3d->z;
-} '... there is no method for z';
-
 $point3d->clear();
 
 is($point3d->x, 0, '... got the right (cleared) value for x');
 is($point3d->y, 0, '... got the right (cleared) value for y');
-is($point3d->{'z'}, 0, '... got the right (cleared) value for z');
+is($point3d->z, 0, '... got the right (cleared) value for z');
 
 dies_ok {
        Point3D->new(x => 10, y => 'Foo', z => 3);
@@ -109,6 +104,10 @@ dies_ok {
        Point3D->new(x => 0, y => 10, z => 'Bar');
 } '... cannot assign a non-Int to z';
 
+dies_ok {
+       Point3D->new(x => 10, y => 3);
+} '... z is a required attribute for Point3D';
+
 # test some class introspection
 
 can_ok('Point', 'meta');
@@ -158,7 +157,7 @@ is_deeply(
        [ 'Point' ],
        '... Point3D gets the parent given to it');
 
-my @Point3D_methods = qw(new meta clear);
+my @Point3D_methods = qw(new meta z clear);
 my @Point3D_attrs   = ('z');
 
 is_deeply(