exercises for section 6
Dave Rolsky [Fri, 19 Jun 2009 21:23:44 +0000 (16:23 -0500)]
moose-class/exercises/answers/06-advanced-attributes/BankAccount.pm [new file with mode: 0644]
moose-class/exercises/answers/06-advanced-attributes/Employee.pm [new file with mode: 0644]
moose-class/exercises/answers/06-advanced-attributes/OutputsXML.pm [new file with mode: 0644]
moose-class/exercises/answers/06-advanced-attributes/Person.pm [new file with mode: 0644]
moose-class/exercises/answers/06-advanced-attributes/Printable.pm [new file with mode: 0644]
moose-class/exercises/t/01-classes.t
moose-class/exercises/t/06-advanced-attributes.t [new file with mode: 0644]
moose-class/exercises/t/lib/MooseClass/Tests.pm

diff --git a/moose-class/exercises/answers/06-advanced-attributes/BankAccount.pm b/moose-class/exercises/answers/06-advanced-attributes/BankAccount.pm
new file mode 100644 (file)
index 0000000..5c0035b
--- /dev/null
@@ -0,0 +1,61 @@
+package BankAccount;
+
+use List::Util qw( sum );
+use Moose;
+
+has balance => (
+    is      => 'rw',
+    isa     => 'Int',
+    default => 100,
+    trigger => sub { $_[0]->_record_difference( $_[1] ) },
+);
+
+has owner => (
+    is       => 'rw',
+    isa      => 'Person',
+    weak_ref => 1,
+);
+
+has history => (
+    is      => 'ro',
+    isa     => 'ArrayRef[Int]',
+    default => sub { [] },
+);
+
+sub BUILD {
+    my $self = shift;
+
+    $self->_record_difference( $self->balance );
+}
+
+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 );
+}
+
+sub _record_difference {
+    my $self      = shift;
+    my $new_value = shift;
+
+    my $old_value = sum @{ $self->history };
+
+    push @{ $self->history }, $new_value - ( $old_value || 0 );
+}
+
+no Moose;
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/moose-class/exercises/answers/06-advanced-attributes/Employee.pm b/moose-class/exercises/answers/06-advanced-attributes/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/06-advanced-attributes/OutputsXML.pm b/moose-class/exercises/answers/06-advanced-attributes/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/06-advanced-attributes/Person.pm b/moose-class/exercises/answers/06-advanced-attributes/Person.pm
new file mode 100644 (file)
index 0000000..ce155f6
--- /dev/null
@@ -0,0 +1,55 @@
+package Person;
+
+use BankAccount;
+use Moose;
+
+with 'Printable', 'OutputsXML';
+
+has account => (
+    is      => 'rw',
+    isa     => 'BankAccount',
+    default => sub { BankAccount->new },
+    handles => [ 'deposit', 'withdraw' ],
+);
+
+has title => (
+    is        => 'rw',
+    predicate => 'has_title',
+    clearer   => 'clear_title',
+);
+
+has first_name => ( is => 'rw' );
+
+has last_name  => ( is => 'rw' );
+
+sub BUILD {
+    my $self = shift;
+
+    $self->account->owner($self);
+}
+
+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/06-advanced-attributes/Printable.pm b/moose-class/exercises/answers/06-advanced-attributes/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;
index e9747fb..4d06f99 100644 (file)
@@ -11,7 +11,6 @@
 # method should return the first and last name separated by a string
 # ("Jon Smith").
 #
-#
 # Create an Employee class in lib/Employee.pm
 #
 # The Employee class is a subclass of Person
diff --git a/moose-class/exercises/t/06-advanced-attributes.t b/moose-class/exercises/t/06-advanced-attributes.t
new file mode 100644 (file)
index 0000000..5af57fd
--- /dev/null
@@ -0,0 +1,46 @@
+# Your tasks ...
+#
+# First, we want to make the account associated with a Person a proper
+# class. Call it BankAccount.
+#
+# This class should have two attributes, "balance", an Int that
+# defaults to 100, and "owner", a Person object.
+#
+# The owner attribute should be a weak reference to prevent cycles.
+#
+# Copy the deposit and withdraw methods from the HasAccount role.
+#
+# Finally, add a read-only history attribute. This will be an ArrayRef
+# of Int's. This should default to an empty array reference.
+#
+# Use a trigger to record the _difference_ after each change to the
+# balance. The previous balance is the sum of all the previous
+# changes. You can use List::Util's sum function to calculate this. To
+# avoid warnings the first time history is recorded, default to 0 if
+# history is empty.
+#
+# Use a BUILD method in BankAccount to record the original balance in
+# the history.
+#
+# We will now delete the HasAccount role entirely. Instead, add an
+# "account" attribute to Person directly.
+#
+# This new account attribute should default to a new BankAccount
+# object. Use delegation so that we can call Person->deposit and
+# Person->withdraw and have it call those methods on the person's
+# BankAccount object.
+#
+# Add a BUILD method to the Person class to set the owner of the
+# Person's bank account to $self.
+
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+use MooseClass::Tests;
+
+use Person;
+use Employee;
+
+MooseClass::Tests::tests06();
index cd5a2ee..0ac9921 100644 (file)
@@ -124,6 +124,36 @@ sub tests04 {
     employee04();
 }
 
+sub tests06 {
+    {
+        local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+        has_meta('BankAccount');
+        no_droppings('BankAccount');
+
+        has_rw_attr( 'BankAccount', 'balance' );
+        has_rw_attr( 'BankAccount', 'owner' );
+        has_ro_attr( 'BankAccount', 'history' );
+    }
+
+    my $person_meta = Person->meta;
+    ok( ! $person_meta->has_attribute('balance'),
+        'Person class does not have a balance attribute' );
+
+    my $deposit_meth = $person_meta->get_method('deposit');
+    isa_ok( $deposit_meth, 'Moose::Meta::Method::Delegation' );
+
+    my $withdraw_meth = $person_meta->get_method('withdraw');
+    isa_ok( $withdraw_meth, 'Moose::Meta::Method::Delegation' );
+
+    my $ba_meta = BankAccount->meta;
+    ok( $ba_meta->get_attribute('owner')->is_weak_ref,
+        'owner attribute is a weak ref' );
+
+    person06();
+}
+
+
 sub has_meta {
     my $class = shift;
 
@@ -350,6 +380,25 @@ EOF
     is( $employee->as_xml, $xml, 'Employee outputs expected XML' );
 }
 
+sub person06 {
+    my $person = Person->new(
+        first_name => 'Bilbo',
+        last_name  => 'Baggins',
+    );
+
+    isa_ok( $person->account, 'BankAccount' );
+    is( $person->account->owner, $person,
+        'owner of bank account is person that created account' );
+
+    $person->deposit(10);
+    is_deeply( $person->account->history, [ 100, 10 ],
+               'deposit was recorded in account history' );
+
+    $person->withdraw(15);
+    is_deeply( $person->account->history, [ 100, 10, -15 ],
+               'withdrawal was recorded in account history' );
+}
+
 sub account_tests {
     local $Test::Builder::Level = $Test::Builder::Level + 1;