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