From: Dave Rolsky Date: Fri, 19 Jun 2009 21:23:44 +0000 (-0500) Subject: exercises for section 6 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=66b226e5fec58c3da576891821da91f4ac809c99;p=gitmo%2Fmoose-presentations.git exercises for section 6 --- 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 index 0000000..5c0035b --- /dev/null +++ b/moose-class/exercises/answers/06-advanced-attributes/BankAccount.pm @@ -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 index 0000000..1bb91fd --- /dev/null +++ b/moose-class/exercises/answers/06-advanced-attributes/Employee.pm @@ -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 index 0000000..db30970 --- /dev/null +++ b/moose-class/exercises/answers/06-advanced-attributes/OutputsXML.pm @@ -0,0 +1,20 @@ +package OutputsXML; + +use Moose::Role; + +requires 'as_xml'; + +around as_xml => sub { + my $orig = shift; + my $self = shift; + + return + qq{\n} . q{<} + . ( ref $self ) . q{>} . "\n" + . ( join "\n", $self->$orig(@_) ) . "\n" . 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 index 0000000..ce155f6 --- /dev/null +++ b/moose-class/exercises/answers/06-advanced-attributes/Person.pm @@ -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 index 0000000..cb9b58c --- /dev/null +++ b/moose-class/exercises/answers/06-advanced-attributes/Printable.pm @@ -0,0 +1,9 @@ +package Printable; + +use Moose::Role; + +requires 'as_string'; + +no Moose::Role; + +1; diff --git a/moose-class/exercises/t/01-classes.t b/moose-class/exercises/t/01-classes.t index e9747fb..4d06f99 100644 --- a/moose-class/exercises/t/01-classes.t +++ b/moose-class/exercises/t/01-classes.t @@ -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 index 0000000..5af57fd --- /dev/null +++ b/moose-class/exercises/t/06-advanced-attributes.t @@ -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(); diff --git a/moose-class/exercises/t/lib/MooseClass/Tests.pm b/moose-class/exercises/t/lib/MooseClass/Tests.pm index cd5a2ee..0ac9921 100644 --- a/moose-class/exercises/t/lib/MooseClass/Tests.pm +++ b/moose-class/exercises/t/lib/MooseClass/Tests.pm @@ -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;