Finished slides & exercises for section 4 (method modifiers)
Dave Rolsky [Wed, 17 Jun 2009 21:57:24 +0000 (16:57 -0500)]
moose-class/exercises/answers/04-method-modifiers/Employee.pm [new file with mode: 0644]
moose-class/exercises/answers/04-method-modifiers/HasAccount.pm [new file with mode: 0644]
moose-class/exercises/answers/04-method-modifiers/OutputsXML.pm [new file with mode: 0644]
moose-class/exercises/answers/04-method-modifiers/Person.pm [new file with mode: 0644]
moose-class/exercises/answers/04-method-modifiers/Printable.pm [new file with mode: 0644]
moose-class/exercises/t/04-method-modifiers.t [new file with mode: 0644]
moose-class/exercises/t/lib/MooseClass/Tests.pm
moose-class/slides/index.html
moose-class/slides/outline
moose-class/slides/ui/custom.css

diff --git a/moose-class/exercises/answers/04-method-modifiers/Employee.pm b/moose-class/exercises/answers/04-method-modifiers/Employee.pm
new file mode 100644 (file)
index 0000000..1bb91fd
--- /dev/null
@@ -0,0 +1,43 @@
+package Employee;
+
+use Moose;
+
+extends 'Person';
+
+has '+title' => (
+    default => 'Worker',
+);
+
+has salary_level => (
+    is      => 'rw',
+    default => 1,
+);
+
+has salary => (
+    is       => 'ro',
+    lazy     => 1,
+    builder  => '_build_salary',
+    init_arg => undef,
+);
+
+has ssn    => ( is => 'ro' );
+
+sub _build_salary {
+    my $self = shift;
+
+    return $self->salary_level * 10000;
+}
+
+augment as_xml => sub {
+    my $self = shift;
+
+    return
+        ( map { "<$_>" . ( $self->$_ || q{} ) . "</$_>" } qw( salary salary_level ssn ) ),
+        inner();
+};
+
+no Moose;
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/moose-class/exercises/answers/04-method-modifiers/HasAccount.pm b/moose-class/exercises/answers/04-method-modifiers/HasAccount.pm
new file mode 100644 (file)
index 0000000..76ea15e
--- /dev/null
@@ -0,0 +1,29 @@
+package HasAccount;
+
+use Moose::Role;
+
+has balance => (
+    is      => 'rw',
+    default => 100,
+);
+
+sub deposit {
+    my $self   = shift;
+    my $amount = shift;
+
+    $self->balance( $self->balance + $amount );
+}
+
+sub withdraw {
+    my $self   = shift;
+    my $amount = shift;
+
+    die "Balance cannot be negative"
+        if $self->balance < $amount;
+
+    $self->balance( $self->balance - $amount );
+}
+
+no Moose::Role;
+
+1;
diff --git a/moose-class/exercises/answers/04-method-modifiers/OutputsXML.pm b/moose-class/exercises/answers/04-method-modifiers/OutputsXML.pm
new file mode 100644 (file)
index 0000000..db30970
--- /dev/null
@@ -0,0 +1,20 @@
+package OutputsXML;
+
+use Moose::Role;
+
+requires 'as_xml';
+
+around as_xml => sub {
+    my $orig = shift;
+    my $self = shift;
+
+    return
+          qq{<?xml version="1.0" encoding="UTF-8"?>\n} . q{<}
+        . ( ref $self ) . q{>} . "\n"
+        . ( join "\n", $self->$orig(@_) ) . "\n" . q{</}
+        . ( ref $self ) . q{>} . "\n";
+};
+
+no Moose::Role;
+
+1;
diff --git a/moose-class/exercises/answers/04-method-modifiers/Person.pm b/moose-class/exercises/answers/04-method-modifiers/Person.pm
new file mode 100644 (file)
index 0000000..e1edc60
--- /dev/null
@@ -0,0 +1,41 @@
+package Person;
+
+use Moose;
+
+with 'Printable', 'HasAccount', 'OutputsXML';
+
+has title => (
+    is        => 'rw',
+    predicate => 'has_title',
+    clearer   => 'clear_title',
+);
+
+has first_name => ( is => 'rw' );
+
+has last_name  => ( is => 'rw' );
+
+sub full_name {
+    my $self = shift;
+
+    my $title = join q{ }, $self->first_name, $self->last_name;
+    $title .= q[ (] . $self->title . q[)]
+        if $self->has_title;
+
+    return $title;
+}
+
+sub as_string { $_[0]->full_name }
+
+sub as_xml {
+    my $self = shift;
+
+    return
+        ( map { "<$_>" . ( $self->$_ || q{} ) . "</$_>" } qw( first_name last_name title ) ),
+        inner();
+}
+
+no Moose;
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/moose-class/exercises/answers/04-method-modifiers/Printable.pm b/moose-class/exercises/answers/04-method-modifiers/Printable.pm
new file mode 100644 (file)
index 0000000..cb9b58c
--- /dev/null
@@ -0,0 +1,9 @@
+package Printable;
+
+use Moose::Role;
+
+requires 'as_string';
+
+no Moose::Role;
+
+1;
diff --git a/moose-class/exercises/t/04-method-modifiers.t b/moose-class/exercises/t/04-method-modifiers.t
new file mode 100644 (file)
index 0000000..8f399bb
--- /dev/null
@@ -0,0 +1,50 @@
+# Your tasks ...
+#
+# You are going to make our Person and Employee classes capable of
+# outputting an XML document describing the object.
+#
+# The document will contain a tag and value for each attribute.
+#
+# You will use method modifiers and roles to achieve this.
+#
+# Start by creating a new role, OutputsXML.
+#
+# This role should require an "as_xml" method in the classes which
+# consume it.
+#
+# This role should also use an around modifier on the as_xml method in
+# order to make sure the document is well-formed XML.
+#
+# This document will look something like this:
+#
+# <?xml version="1.0" encoding="UTF-8"?>
+# <Person>
+# <first_name>Joe</first_name>
+# <last_name>Smith</last_name>
+# </Person>
+#
+# Use the role to create the xml declaration (the first line) and the
+# container tags (<person> or <employee)
+#
+# The classes should return a list strings. Each string should be a
+# tagged value for an attribute. For consistency, return the
+# attributes in sorted order.
+#
+# ( '<first_name>Joe</first_name>', '<last_name>Smith</last_name>' )
+#
+# If an attribute is empty, just output an empty tag (<foo></foo>).
+#
+# Use an augment modifier in the Person and Employee classes to allow
+# Employee to return just its own attributes.
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+use MooseClass::Tests;
+
+use Person;
+use Employee;
+
+MooseClass::Tests::tests04();
index 5455a27..cd5a2ee 100644 (file)
@@ -105,6 +105,25 @@ sub tests03 {
     employee03();
 }
 
+sub tests04 {
+    {
+        local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+        no_droppings('OutputsXML');
+
+        does_role( 'Person', 'OutputsXML' );
+    }
+
+    ok( scalar OutputsXML->meta->get_around_method_modifiers('as_xml'),
+        'OutputsXML has an around modifier for as_xml' );
+
+    isa_ok( Employee->meta->get_method('as_xml'),
+            'Moose::Meta::Method::Augmented', 'as_xml is augmented in Employee' );
+
+    person04();
+    employee04();
+}
+
 sub has_meta {
     my $class = shift;
 
@@ -289,6 +308,48 @@ sub employee03 {
         'salary is calculated from salary_level, and salary passed to constructor is ignored' );
 }
 
+
+sub person04 {
+    my $person = Person->new(
+        first_name => 'Bilbo',
+        last_name  => 'Baggins',
+    );
+
+    my $xml = <<'EOF';
+<?xml version="1.0" encoding="UTF-8"?>
+<Person>
+<first_name>Bilbo</first_name>
+<last_name>Baggins</last_name>
+<title></title>
+</Person>
+EOF
+
+    is( $person->as_xml, $xml, 'Person outputs expected XML' );
+}
+
+sub employee04 {
+    my $employee = Employee->new(
+        first_name   => 'Jimmy',
+        last_name    => 'Foo',
+        ssn          => '123-99-4567',
+        salary_level => 3,
+    );
+
+    my $xml = <<'EOF';
+<?xml version="1.0" encoding="UTF-8"?>
+<Employee>
+<first_name>Jimmy</first_name>
+<last_name>Foo</last_name>
+<title>Worker</title>
+<salary>30000</salary>
+<salary_level>3</salary_level>
+<ssn>123-99-4567</ssn>
+</Employee>
+EOF
+
+    is( $employee->as_xml, $xml, 'Employee outputs expected XML' );
+}
+
 sub account_tests {
     local $Test::Builder::Level = $Test::Builder::Level + 1;
 
index 2ff609a..2ddbdc1 100644 (file)
@@ -214,7 +214,7 @@ use Moose;
 </div>
 
 <div class="slide">
-  <h1>Before &amp; After</h1>
+  <h1>Before and After</h1>
 
 <pre><code>before 'foo'
     =&gt; sub { warn 'About to call foo()' };
@@ -932,7 +932,7 @@ extends 'LWP';</code></pre>
   <pre><code>package Employee;
 use Moose;
 
-<span class="current">extends 'Person';</span>
+<span class="current incremental">extends 'Person';</span>
 
 <span class="incremental">overrides</span> work =&gt; sub {
     my $self = shift;
@@ -1088,6 +1088,10 @@ use Moose;
 </div>
 
 <div class="slide">
+  <h1>Questions?</h1>
+</div>  
+
+<div class="slide">
   <h1>Exercises</h1>
 
   <pre># cd exercises
@@ -1119,7 +1123,7 @@ Iterate til this passes all its tests</pre>
   <pre><code>package HasPermissions;
 use Moose::Role;
 
-<span class="current"># state
+<span class="current incremental"># state
 has access_level =&gt; ( is =&gt; 'rw' );</span>
 
 <span class="incremental"># behavior
@@ -1431,7 +1435,7 @@ with 'Killer';</code></pre>
 <pre><code>package HasSize;
 use Moose::Role;
 
-<span class="current">requires 'size';</span>
+<span class="current incremental">requires 'size';</span>
 
 package Shirt;
 use Moose;
@@ -1573,6 +1577,23 @@ requires 'compare';
 </div>
 
 <div class="slide">
+  <h1>Roles Summary</h1>
+
+  <ul>
+    <li>Roles can define an interface with <code>requires</code></li>
+    <li>Roles can have state (attributes) and behavior (methods)</li>
+    <li>Roles can mix interface, state, &amp; behavior</li>
+    <li>Roles are composed (flattened) into classes</li>
+    <li>Roles can do other roles</li>
+    <li>Roles can be used as a type in APIs (must do Comparable)</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Questions?</h1>
+</div>  
+
+<div class="slide">
   <h1>Exercises</h1>
 
   <pre># cd exercises
@@ -1626,7 +1647,7 @@ use Moose;
 
 has first_name =&gt; (
     is       =&gt; 'ro',
-    <span class="current">required =&gt; 1,</span>
+    <span class="current incremental">required =&gt; 1,</span>
 );
 
 <span class="incremental">Person->new( first_name =&gt; undef ); # ok
@@ -2055,6 +2076,24 @@ has first_name =&gt; (
 </div>
 
 <div class="slide">
+  <h1>Basic Attributes Summary</h1>
+
+  <ul>
+    <li>Attributes can be <code>required</code></li>
+    <li>Attributes can have a <code>default</code> or <code>builder</code></li>
+    <li>Attributes with a default or builder can be <code>lazy</code></li>
+    <li>Attributes can have a <code>clearer</code> and/or <code>predicate</code></li>
+    <li>An attribute's constructor name can be changed with <code>init_arg</code></li>
+    <li>A subclass can alter its parents' attributes</li>
+    <li>Attribute accessor names can be changed</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Questions?</h1>
+</div>  
+
+<div class="slide">
   <h1>Exercises</h1>
 
   <pre># cd exercises
@@ -2063,6 +2102,279 @@ has first_name =&gt; (
 Iterate til this passes all its tests</pre>
 </div>
 
+<div class="slide fake-slide0">
+  <h1>Part 4: Method Modifiers</h1>
+</div>
+
+<div class="slide">
+  <h1>What is a Method Modifier</h1>
+
+  <ul>
+    <li>Apply to an existing method</li>
+    <li>... from a parent class, the current class, or a role</li>
+    <li>Roles can provide modifiers that are applied at composition time</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>What is a Method Modifier</h1>
+
+  <ul>
+    <li>"Iinject" behavior</li>
+    <li>Add behavior to generated methods (accessors, delegations)</li>
+    <li>Provide roles which modify existing behavior</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Before and After</h1>
+
+  <ul>
+    <li>Simplest modifiers - <code>before</code> and <code>after</code></li>
+    <li>Guess when they run!</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Uses for <code>before</code></h1>
+
+  <ul>
+    <li>As a pre-call check</li>
+  </ul>
+
+  <pre><code>package Person;
+use Moose;
+
+before work =&gt; sub {
+    my $self = shift;
+    die 'I have no job!'
+        unless $self-&gt;has_title;
+};</code></pre>
+</div>    
+
+<div class="slide">
+  <h1>Uses for <code>before</code></h1>
+
+  <ul>
+    <li>Logging/Debugging</li>
+  </ul>
+
+  <pre><code>package Person;
+use Moose;
+
+before work =&gt; sub {
+    my $self = shift;
+    return unless $DEBUG;
+
+    warn "Called work on ", $self->full_name,
+         "with the arguments: [@_]\n";
+};</code></pre>
+</div>    
+
+<div class="slide">
+  <h1>Uses for <code>after</code></h1>
+
+  <ul>
+    <li>Also works for logging/debugging</li>
+    <li>Post-X side-effects (recording audit info)</li>
+  </ul>
+
+  <pre><code>package Person;
+use Moose;
+
+after work =&gt; sub {
+    my $self = shift;
+    $self-&gt;work_count(
+        $self-&gt;work_count + 1 );
+};</code></pre>
+</div>
+
+<div class="slide">
+  <h1>Other Uses</h1>
+
+  <ul>
+    <li>Modifiers are useful for adding behavior to generated methods</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Other Uses Example</h1>
+
+  <pre><code>has password =&gt; (
+     is      =&gt; 'rw',
+     clearer =&gt; 'clear_password',
+);
+
+has hashed_password =&gt; (
+     is      =&gt; 'ro',
+     builder =&gt; '_build_hashed_password',
+     clearer =&gt; '_clear_hashed_password',
+);
+
+after clear_password =&gt; sub {
+    my $self = shift;
+    $self-&gt;_clear_hashed_password;
+};</code></pre>
+</div>
+
+<div class="slide">
+  <h1><code>before</code> and <code>after</code> Limitations</h1>
+
+  <ul>
+    <li>Cannot alter method parameters</li>
+    <li>Cannot alter return value</li>
+    <li>But <strong>can</strong> throw an exception</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>The <code>around</code> Modifier</h1>
+
+  <ul>
+    <li>The big gun</li>
+    <li>Can alter parameters <strong>and/or</strong> return values</li>
+    <li>Can skip calling the wrapped method entirely</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>The power of <code>around</code></h1>
+
+  <pre><code>around insert =&gt; sub {
+    my $orig = shift;
+    my $self = shift;
+
+    $self-&gt;_validate_insert(@_);
+
+    my $new_user =
+        $self-&gt;$orig(
+            $self-&gt;_munge_insert(@_) );
+
+    $new_user->_assign_uri;
+
+    return $new_user;
+};</code></pre>
+</div>
+
+<div class="slide">
+  <h1>Modifier Order</h1>
+
+  <ul>
+    <li>Before runs order from last to first</li>
+    <li>After runs in order from first to last</li>
+    <li>Around runs in order from last to first</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Modifier Order Illustrated</h1>
+
+<pre>
+<span class="current incremental">before 2
+ before 1</span>
+  <span class="incremental">around 2
+   around 1</span>
+    <span class="incremental">wrapped method</span>
+   <span class="incremental">around 1
+  around 2</span>
+ <span class="incremental">after 1
+after 2</span>
+</pre>
+</div>
+
+<div class="slide">
+  <h1>Modifiers in Roles</h1>
+
+  <ul>
+    <li>Roles can use these modifiers</li>
+    <li>Very powerful!</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Modifiers in Roles</h1>
+
+  <pre><code>package IsUnreliable;
+use Moose::Role;
+
+<span class="highlight">requires 'run';
+
+around run</span> =&gt; sub {
+    my $orig = shift;
+    my $self = shift;
+
+    return if rand(1) &lt; 0.5;
+
+    return $self-&gt;$orig(@_);
+};</code></pre>
+</div>
+
+<div class="slide">
+  <h1>Augment and Inner</h1>
+
+  <ul>
+    <li>Inverted <code>super</code></li>
+    <li>From least- to most-specific</li>
+    <li>Grandparent to parent to child</li>
+    <li>Not allowed in roles</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Augment and Inner</h1>
+
+  <pre><code>package Document;
+
+sub xml { '&lt;doc&gt;' . <span class="highlight">inner()</span> . '&lt;/doc&gt;' }
+
+package Report;
+extends 'Document';
+
+<span class="highlight">augment xml</span> =&gt; { title() . <span class="highlight">inner()</span> . summary() };
+
+package TPSReport;
+extends 'Report';
+
+<span class="highlight">augment xml</span> =&gt; { tps_xml() . <span class="highlight">inner()</span> };</code></pre>
+</div>
+
+<div class="slide">
+  <h1>Augment and Inner</h1>
+
+  <ul>
+    <li>When we call <code>$tps-&gt;xml</code> ...
+      <ul>
+        <li><code>Document-&gt;xml</code></li>
+        <li><code>Report-&gt;xml</code></li>
+        <li><code>TPSReport-&gt;xml</code></li>
+      </ul>
+    </li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Augment and Inner Usage</h1>
+
+  <ul>
+    <li>Call <code>inner()</code> to "fill in the blank"</li>
+    <li>Requires designing for subclassing</li>
+    <li>Call <code>inner()</code> in the terminal class, just in case</li>
+  </ul>
+</div>
+
+<div class="slide">
+  <h1>Questions?</h1>
+</div>  
+
+<div class="slide">
+  <h1>Exercises</h1>
+
+  <pre># cd exercises
+# perl bin/prove -lv t/04-method-modifiers.t
+
+Iterate til this passes all its tests</pre>
+</div>
+
 </div> 
 </body>
 </html>
@@ -2076,4 +2388,3 @@ This work is licensed under a Creative Commons Attribution-Share Alike
 http://creativecommons.org/licenses/by-sa/3.0/us/ for details.
 
 -->
-
index 338e312..789baa9 100644 (file)
 ** after for additional state changes
 * around
 ** modifying arguments & return values
-
-== Exercises
-
-= More roles
-
-* methods and attributes
-* method modifiers (and requiring the wrapped method)
-* attributes in roles
+* method modifiers in roles
+** requiring the wrapped method
+* augment/inner
 
 == Exercises
 
 
 == Exercises
 
-= MooseX
+= Tour of MooseX
+
+= Writing a MooseX Module
 
 == Exercises
index 29fd936..55b9af9 100644 (file)
@@ -53,6 +53,10 @@ img.for-slide {
     line-height: 1.2em;
 }
 
+.slide li {
+    margin-top: 0.5em;
+}
+
 .slide pre {
     font-size: 100%;
     line-height: 110%;
@@ -63,6 +67,11 @@ img.for-slide {
     color: #03c;
 }
 
+.delete {
+    text-decoration: line-through;
+    color: #eee;
+}
+
 .slide pre.small {
     font-size: 33%;
 }