Add Moose::Unsweetened, parallel examples of classes with & without
[gitmo/Moose.git] / lib / Moose / Unsweetened.pod
CommitLineData
b8790e44 1=pod
2
3=head1 NAME
4
5Moose::Unsweetened - Moose idioms in plain old Perl 5 without the sugar
6
7=head1 DESCRIPTION
8
9If you're trying to figure out just what the heck Moose does, and how
10it saves you time, you might find it helpful to see what Moose is
11I<really> doing for you. This document shows you the translation from
12Moose sugar back to plain old Perl 5.
13
14=head1 CLASSES AND ATTRIBUTES
15
16First, we define two very small classes the Moose way.
17
18 package Person;
19
20 use DateTime;
21 use DateTime::Format::Natural;
22 use Moose;
23 use Moose::Util::TypeConstraints;
24
25 has name => (
26 is => 'rw',
27 isa => 'Str',
28 required => 1,
29 );
30
31 # Moose doesn't know about non-Moose-based classes.
32 class_type 'DateTime';
33
34 my $en_parser = DateTime::Format::Natural->new(
35 lang => 'en',
36 time_zone => 'UTC',
37 );
38
39 coerce 'DateTime'
40 => from 'Str'
41 => via { $en_parser->parse_datetime($_) };
42
43 has birth_date => (
44 is => 'rw',
45 isa => 'DateTime',
46 );
47
48 subtype 'ShirtSize'
49 => as 'Str'
50 => where { /^(?:s|m|l|xl|xxl)$/i }
51 => message { "$_ is not a valid shirt size (s, m, l, xl, xxl)" };
52
53 has shirt_size => (
54 is => 'rw',
55 isa => 'ShirtSize',
56 default => 'l',
57 );
58
59This is a fairly simple class with three attributes. We also define a
60type to validate t-shirt sizes because we don't want to end up with
61something like "blue" for the shirt size!
62
63 package User;
64
65 use Email::Valid;
66 use Moose;
67 use Moose::Util::TypeConstraints;
68
69 extends 'Person';
70
71 subtype 'Email'
72 => as 'Str'
73 => where { Email::Valid->address($_) }
74 => message { "$_ is not a valid email address" };
75
76 has email_address => (
77 is => 'rw',
78 isa => 'Email',
79 required => 1,
80 );
81
82This class subclasses Person to add a single attribute, email address.
83
84Now we will show what these classes would look like in plain old Perl
855. For the sake of argument, we won't use any base classes or any
86helpers like C<Class::Accessor>.
87
88 package Person;
89
90 use strict;
91 use warnings;
92
93 use Carp qw( confess );
94 use DateTime;
95 use DateTime::Format::Natural;
96
97
98 sub new {
99 my $class = shift;
100 my %p = ref $_[0] ? %{ $_[0] } : @_;
101
102 exists $p{name}
103 or confess 'name is a required attribute';
104 $class->_validate_name( $p{name} );
105
106 exists $p{birth_date}
107 or confess 'birth_date is a required attribute';
108
109 my $date = $p{birth_date};
110 $class->_coerce_birth_date( \$date );
111 $class->_validate_birth_date( $date );
112
113 $p{shirt_size} = 'l'
114 unless exists $p{shirt_size}:
115
116 $class->_validate_shirt_size( $p{shirt_size} );
117
118 my $self = map { $_ => $p{$_} } qw( name shirt_size );
119 $self->{birth_date} = $date;
120
121 return bless $self, $class;
122 }
123
124 sub _validate_name {
125 shift;
126 my $name = shift;
127
128 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
129
130 defined $name
131 or confess 'name must be a string';
132 }
133
134 {
135 my $en_parser = DateTime::Format::Natural->new(
136 lang => 'en',
137 time_zone => 'UTC',
138 );
139
140 sub _coerce_birth_date {
141 shift;
142 my $date = shift;
143
144 return unless defined $date && ! ref $date;
145
146 my $dt = $en_parser->parse_datetime($date);
147
148 return $dt ? $dt : undef;
149 }
150 }
151
152 sub _validate_birth_date {
153 shift;
154 my $birth_date = shift;
155
156 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
157
158 $birth_date->isa('DateTime') )
159 or confess 'birth_date must be a DateTime object';
160 }
161
162 sub _validate_shirt_size {
163 shift;
164 my $shirt_size = shift;
165
166 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
167
168 defined $shirt_size
169 or confess 'shirt_size cannot be undef';
170
171 $shirt_size =~ /^(?:s|m|l|xl|xxl)$/
172 or confess "$shirt_size is not a valid shirt size (s, m, l, xl, xxl)";
173 }
174
175 sub name {
176 my $self = shift;
177
178 if (@_) {
179 $self->_validate_name( $_[0] );
180 $self->{name} = $_[0];
181 }
182
183 return $self->{name};
184 }
185
186 sub birth_date {
187 my $self = shift;
188
189 if (@_) {
190 my $date = shift;
191
192 $self->_coerce_birth_date( $date );
193 $self->_validate_birth_date( $date );
194 $self->{birth_date} = $date;
195 }
196
197 return $self->{birth_date};
198 }
199
200 sub shirt_size {
201 my $self = shift;
202
203 if (@_) {
204 $self->_validate_shirt_size( $_[0] );
205 $self->{shirt_size} = $_[0];
206 }
207
208 return $self->{shirt_size};
209 }
210
211Wow, that was a mouthful! One thing to note is just how much space the
212data validation code consumes. As a result, it's pretty common for
213Perl 5 programmers to just not bother, which results in much more
214fragile code.
215
216Did you spot the bug?
217
218It's in the C<_validate_birth_date()> method. We should check that
219that value in C<$birth_date> is actually defined and object before we
220go and call C<isa()> on it! Leaving out those checks means our data
221validation code could actually cause our program to die. Oops.
222
223There's one bit of code in there worth explaining, which is the
224handling of the birth date for coercion. In both the constructor and
225accessor, we first take a copy of the birth date before passing it to
226the coercion routine. This is to avoid changing the value as it was
227passed to those methods, which could cause problems for the caller.
228
229Also note that if we add a superclass to Person we'll have to change
230the constructor to account for that.
231
232(As an aside, getting all the little details of what Moose does for
233you just right in this code was not easy, which just emphasizes the
234point, that Moose saves you a lot of work!)
235
236Now let's see User:
237
238 package User;
239
240 use strict;
241 use warnings;
242
243 use Carp qw( confess );
244 use Email::Valid;
245 use Scalar::Util qw( blessed );
246
247 use base 'Person';
248
249
250 sub new {
251 my $class = shift;
252 my %p = ref $_[0] ? %{ $_[0] } : @_;
253
254 exists $p{email_address}
255 or confess 'email_address is a required attribute';
256 $class->_validate_email_address( $p{email_address} );
257
258 my $self = $class->SUPER::new(%p);
259
260 $self->{email_address} = $p{email_address};
261
262 return $self;
263 }
264
265 sub _validate_email_address {
266 shift;
267 my $email_address = shift;
268
269 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
270
271 defined $email_address
272 or confess 'email_address must be a string';
273
274 Email::Valid->address($email_address)
275 or confess "$email_address is not a valid email address";
276 }
277
278 sub email_address {
279 my $self = shift;
280
281 if (@_) {
282 $self->_validate_email_address( $_[0] );
283 $self->{email_address} = $_[0];
284 }
285
286 return $self->{email_address};
287 }
288
289That one was shorter, but it only has one attribute.
290
291Between the two classes, we have a whole lot of code that doesn't do
292much. We could probably simplify this by defining some sort of
293"attribute and validation" hash, like this:
294
295 package Person;
296
297 my %Attr = (
298 name => {
299 required => 1,
300 validate => sub { defined $_ },
301 },
302 birth_date => {
303 required => 1,
304 validate => sub { blessed $_ && $_->isa('DateTime') },
305 },
306 shirt_size => {
307 required => 1,
308 validate => sub { defined $_ && $_ =~ /^(?:s|m|l|xl|xxl)$/i },
309 }
310 );
311
312Then we could define a base class that would accept such a definition,
313and do the right thing. Keep that sort of thing up and you're well on
314your way to writing a half-assed version of Moose!
315
316Of course, there are CPAN modules that do some of what Moose does,
317like C<Class::Accessor>, C<Class::Meta>, and so on. But none of them
318put together all of Moose's features along with a layer of declarative
319sugar.
320
321=head1 AUTHOR
322
323Dave Rolsky E<lt>autarch@urth.orgE<gt>
324
325=head1 COPYRIGHT AND LICENSE
326
327Copyright 2008 by Infinity Interactive, Inc.
328
329L<http://www.iinteractive.com>
330
331This library is free software; you can redistribute it and/or modify
332it under the same terms as Perl itself.
333
334=cut