--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+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;
--- /dev/null
+package Printable;
+
+use Moose::Role;
+
+requires 'as_string';
+
+no Moose::Role;
+
+1;
# 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
--- /dev/null
+# 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();
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;
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;