Redid conversion to Test::Fatal
[gitmo/Moose.git] / lib / Moose / Cookbook / Basics / Recipe2.pod
1
2 =pod
3
4 =head1 NAME
5
6 Moose::Cookbook::Basics::Recipe2 - A simple B<BankAccount> example
7
8 =head1 SYNOPSIS
9
10   package BankAccount;
11   use Moose;
12
13   has 'balance' => ( isa => 'Int', is => 'rw', default => 0 );
14
15   sub deposit {
16       my ( $self, $amount ) = @_;
17       $self->balance( $self->balance + $amount );
18   }
19
20   sub withdraw {
21       my ( $self, $amount ) = @_;
22       my $current_balance = $self->balance();
23       ( $current_balance >= $amount )
24           || confess "Account overdrawn";
25       $self->balance( $current_balance - $amount );
26   }
27
28   package CheckingAccount;
29   use Moose;
30
31   extends 'BankAccount';
32
33   has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' );
34
35   before 'withdraw' => sub {
36       my ( $self, $amount ) = @_;
37       my $overdraft_amount = $amount - $self->balance();
38       if ( $self->overdraft_account && $overdraft_amount > 0 ) {
39           $self->overdraft_account->withdraw($overdraft_amount);
40           $self->deposit($overdraft_amount);
41       }
42   };
43
44 =head1 DESCRIPTION
45
46 The first recipe demonstrated how to build very basic Moose classes,
47 focusing on creating and manipulating attributes. The objects in that
48 recipe were very data-oriented, and did not have much in the way of
49 behavior (i.e. methods). In this recipe, we expand upon the concepts
50 from the first recipe to include some real behavior. In particular, we
51 show how you can use a method modifier to implement new behavior for a
52 method.
53
54 The classes in the SYNOPSIS show two kinds of bank account. A simple
55 bank account has one attribute, the balance, and two behaviors,
56 depositing and withdrawing money.
57
58 We then extend the basic bank account in the CheckingAccount
59 class. This class adds another attribute, an overdraft account. It
60 also adds overdraft protection to the withdraw method. If you try to
61 withdraw more than you have, the checking account attempts to
62 reconcile the difference by withdrawing money from the overdraft
63 account. (1)
64
65 The first class, B<BankAccount>, introduces a new attribute feature, a
66 default value:
67
68   has 'balance' => ( isa => 'Int', is => 'rw', default => 0 );
69
70 This says that a B<BankAccount> has a C<balance> attribute, which has
71 an C<Int> type constraint, a read/write accessor, and a default value
72 of C<0>. This means that every instance of B<BankAccount> that is
73 created will have its C<balance> slot initialized to C<0>, unless some
74 other value is provided to the constructor.
75
76 The C<deposit> and C<withdraw> methods should be fairly
77 self-explanatory, as they are just plain old Perl 5 OO.
78
79 As you know from the first recipe, the keyword C<extends> sets a
80 class's superclass. Here we see that B<CheckingAccount> C<extends>
81 B<BankAccount>. The next line introduces yet another new attribute
82 feature, class-based type constraints:
83
84   has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' );
85
86 Up until now, we have only seen the C<Int> type constraint, which (as
87 we saw in the first recipe) is a builtin type constraint. The
88 C<BankAccount> type constraint is new, and was actually defined the
89 moment we created the B<BankAccount> class itself. In fact, Moose
90 creates a corresponding type constraint for every class in your
91 program (2).
92
93 This means that in the first recipe, constraints for both C<Point> and
94 C<Point3D> were created. In this recipe, both C<BankAccount> and
95 C<CheckingAccount> type constraints are created automatically. Moose
96 does this as a convenience so that your classes and type constraint
97 can be kept in sync with one another. In short, Moose makes sure that
98 it will just DWIM (3).
99
100 In B<CheckingAccount>, we see another method modifier, the C<before>
101 modifier.
102
103   before 'withdraw' => sub {
104       my ( $self, $amount ) = @_;
105       my $overdraft_amount = $amount - $self->balance();
106       if ( $self->overdraft_account && $overdraft_amount > 0 ) {
107           $self->overdraft_account->withdraw($overdraft_amount);
108           $self->deposit($overdraft_amount);
109       }
110   };
111
112 Just as with the C<after> modifier from the first recipe, Moose will
113 handle calling the superclass method (in this case C<<
114 BankAccount->withdraw >>).
115
116 The C<before> modifier will (obviously) run I<before> the code from
117 the superclass is run. Here, C<before> modifier implements overdraft
118 protection by first checking if there are available funds in the
119 checking account. If not (and if there is an overdraft account
120 available), it transfers the amount needed into the checking
121 account (4).
122
123 As with the method modifier in the first recipe, we could use
124 C<SUPER::> to get the same effect:
125
126   sub withdraw {
127       my ( $self, $amount ) = @_;
128       my $overdraft_amount = $amount - $self->balance();
129       if ( $self->overdraft_account && $overdraft_amount > 0 ) {
130           $self->overdraft_account->withdraw($overdraft_amount);
131           $self->deposit($overdraft_amount);
132       }
133       $self->SUPER::withdraw($amount);
134   }
135
136 The benefit of taking the method modifier approach is we do not need
137 to remember to call C<SUPER::withdraw> and pass it the C<$amount>
138 argument when writing C<< CheckingAccount->withdraw >>.
139
140 This is actually more than just a convenience for forgetful
141 programmers. Using method modifiers helps isolate subclasses from
142 changes in the superclasses. For instance, if B<<
143 BankAccount->withdraw >> were to add an additional argument of some
144 kind, the version of B<< CheckingAccount->withdraw >> which uses
145 C<SUPER::withdraw> would not pass that extra argument correctly,
146 whereas the method modifier version would automatically pass along all
147 arguments correctly.
148
149 Just as with the first recipe, object instantiation uses the C<new>
150 method, which accepts named parameters.
151
152   my $savings_account = BankAccount->new( balance => 250 );
153
154   my $checking_account = CheckingAccount->new(
155       balance           => 100,
156       overdraft_account => $savings_account,
157   );
158
159 And as with the first recipe, a more in-depth example can be found in
160 the F<t/000_recipes/moose_cookbook_basics_recipe2.t> test file.
161
162 =head1 CONCLUSION
163
164 This recipe expanded on the basic concepts from the first recipe with
165 a more "real world" use case.
166
167 =head1 FOOTNOTES
168
169 =over 4
170
171 =item (1)
172
173 If you're paying close attention, you might realize that there's a
174 circular loop waiting to happen here. A smarter example would have to
175 make sure that we don't accidentally create a loop between the
176 checking account and its overdraft account.
177
178 =item (2)
179
180 In reality, this creation is sensitive to the order in which modules
181 are loaded. In more complicated cases, you may find that you need to
182 explicitly declare a class type before the corresponding is loaded.
183
184 =item (3)
185
186 Moose does not attempt to encode a class's is-a relationships within
187 the type constraint hierarchy. Instead, Moose just considers the class
188 type constraint to be a subtype of C<Object>, and specializes the
189 constraint check to allow for subclasses. This means that an instance
190 of B<CheckingAccount> will pass a C<BankAccount> type constraint
191 successfully. For more details, please refer to the
192 L<Moose::Util::TypeConstraints> documentation.
193
194 =item (4)
195
196 If the overdraft account does not have the amount needed, it will
197 throw an error. Of course, the overdraft account could also have
198 overdraft protection. See note 1.
199
200 =back
201
202 =head1 SEE ALSO
203
204 =over 4
205
206 =item Acknowledgment
207
208 The BankAccount example in this recipe is directly taken from the
209 examples in this chapter of "Practical Common Lisp":
210
211 L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html>
212
213 =back
214
215 =head1 AUTHORS
216
217 Stevan Little E<lt>stevan@iinteractive.comE<gt>
218
219 Dave Rolsky E<lt>autarch@urth.orgE<gt>
220
221 =head1 COPYRIGHT AND LICENSE
222
223 Copyright 2006-2010 by Infinity Interactive, Inc.
224
225 L<http://www.iinteractive.com>
226
227 This library is free software; you can redistribute it and/or modify
228 it under the same terms as Perl itself.
229
230 =begin testing
231
232 my $savings_account;
233
234 {
235     $savings_account = BankAccount->new( balance => 250 );
236     isa_ok( $savings_account, 'BankAccount' );
237
238     is( $savings_account->balance, 250, '... got the right savings balance' );
239     is(
240         exception {
241             $savings_account->withdraw(50);
242         },
243         undef,
244         '... withdrew from savings successfully'
245     );
246     is( $savings_account->balance, 200,
247         '... got the right savings balance after withdrawl' );
248
249     $savings_account->deposit(150);
250     is( $savings_account->balance, 350,
251         '... got the right savings balance after deposit' );
252 }
253
254 {
255     my $checking_account = CheckingAccount->new(
256         balance           => 100,
257         overdraft_account => $savings_account
258     );
259     isa_ok( $checking_account, 'CheckingAccount' );
260     isa_ok( $checking_account, 'BankAccount' );
261
262     is( $checking_account->overdraft_account, $savings_account,
263         '... got the right overdraft account' );
264
265     is( $checking_account->balance, 100,
266         '... got the right checkings balance' );
267
268     is(
269         exception {
270             $checking_account->withdraw(50);
271         },
272         undef,
273         '... withdrew from checking successfully'
274     );
275     is( $checking_account->balance, 50,
276         '... got the right checkings balance after withdrawl' );
277     is( $savings_account->balance, 350,
278         '... got the right savings balance after checking withdrawl (no overdraft)'
279     );
280
281     is(
282         exception {
283             $checking_account->withdraw(200);
284         },
285         undef,
286         '... withdrew from checking successfully'
287     );
288     is( $checking_account->balance, 0,
289         '... got the right checkings balance after withdrawl' );
290     is( $savings_account->balance, 200,
291         '... got the right savings balance after overdraft withdrawl' );
292 }
293
294 {
295     my $checking_account = CheckingAccount->new(
296         balance => 100
297
298             # no overdraft account
299     );
300     isa_ok( $checking_account, 'CheckingAccount' );
301     isa_ok( $checking_account, 'BankAccount' );
302
303     is( $checking_account->overdraft_account, undef,
304         '... no overdraft account' );
305
306     is( $checking_account->balance, 100,
307         '... got the right checkings balance' );
308
309     is(
310         exception {
311             $checking_account->withdraw(50);
312         },
313         undef,
314         '... withdrew from checking successfully'
315     );
316     is( $checking_account->balance, 50,
317         '... got the right checkings balance after withdrawl' );
318
319     isnt(
320         exception {
321             $checking_account->withdraw(200);
322         },
323         undef,
324         '... withdrawal failed due to attempted overdraft'
325     );
326     is( $checking_account->balance, 50,
327         '... got the right checkings balance after withdrawl failure' );
328 }
329
330 =end testing
331
332 =cut