File Coverage

blib/lib/MooseX/Prototype.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1 2     2   30298 use 5.008;
  2         137  
  2         84  
2 2     2   10 use strict;
  2         3  
  2         59  
3 2     2   10 use warnings;
  2         7  
  2         59  
4              
5 2     2   2471 use Moose 2.00 ();
  0            
  0            
6             use Data::OptList 0 ();
7             use Sub::Exporter 0 ();
8              
9             my $serial = 0;
10             my $serial_name = sub {
11             sprintf('MooseX::Prototype::__ANON__::%04d', ++$serial);
12             };
13              
14             my $mk_attribute = sub {
15             my ($name, $rw) = @_;
16             Moose::Meta::Attribute::->new($name, is => ($rw||'rw'), isa => 'Any');
17             };
18              
19             my $cloned_attributes = sub {
20             return [
21             map {
22             my $attr = $_;
23             my @clone = ();
24             if ($attr->has_value($_[0]))
25             {
26             my $value = $attr->get_value($_[0]);
27             @clone = ( default => sub{$value} );
28             }
29             $attr->clone(@clone);
30             } $_[0]->meta->get_all_attributes
31             ]
32             };
33              
34             BEGIN {
35             package MooseX::Prototype;
36             our $AUTHORITY = 'cpan:TOBYINK';
37             our $VERSION = '0.004';
38             $INC{'MooseX/Prototype.pm'} = __FILE__;
39              
40             use Sub::Exporter -setup => {
41             exports => [
42             create_class_from_prototype => \&_build_create_class_from_prototype,
43             object => \&_build_object,
44             ],
45             groups => {
46             default => [qw/ object /],
47             },
48             };
49            
50             sub _build_create_class_from_prototype
51             {
52             my ($class, $name, $arg) = @_;
53            
54             my $IS = $arg->{ -is } || 'rw';
55             my $BASE = $arg->{ -base } || 'Moose::Object';
56             my $ROLE = $arg->{ -role } || (
57             $IS eq 'ro'
58             ? 'MooseX::Prototype::Trait::Object::RO'
59             : 'MooseX::Prototype::Trait::Object::RW'
60             );
61            
62             return sub
63             {
64             my ($instance, $opts) = @_;
65             $opts = { name => $opts } if defined $opts && !ref $opts;
66            
67             $opts->{name} ||= $serial_name->();
68            
69             Moose::Meta::Class::->create(
70             $opts->{name},
71             superclasses => [ ref $instance ],
72             roles => [ $ROLE ],
73             attributes => $instance->$cloned_attributes,
74             );
75             return $opts->{name};
76             }
77             }
78            
79             sub _build_object
80             {
81             my ($class, $name, $arg) = @_;
82            
83             my $IS = $arg->{ -is } || 'rw';
84             my $BASE = $arg->{ -base } || 'Moose::Object';
85             my $ROLE = $arg->{ -role } || (
86             $IS eq 'ro'
87             ? 'MooseX::Prototype::Trait::Object::RO'
88             : 'MooseX::Prototype::Trait::Object::RW'
89             );
90            
91             return sub ($)
92             {
93             my $hash = ref $_[0] ? shift : +{@_};
94             my $class = Moose::Meta::Class::->create(
95             $serial_name->(),
96             superclasses => [ $BASE ],
97             roles => [ $ROLE ],
98             attributes => [
99             map { $mk_attribute->($_, $IS) }
100             grep { not /^\&/ }
101             keys %$hash
102             ],
103             methods => {
104             map { ; substr($_, 1) => $hash->{$_} }
105             grep { /^\&/ }
106             keys %$hash
107             },
108             );
109             return $class->name->new({
110             map { ; $_ => $hash->{$_} }
111             grep { not /^\&/ }
112             keys %$hash
113             });
114             }
115             }
116            
117             *create_class_from_prototype = __PACKAGE__->_build_create_class_from_prototype;
118             *object = __PACKAGE__->_build_object;
119             };
120              
121             BEGIN {
122             package MooseX::Prototype::Trait::Object;
123             our $AUTHORITY = 'cpan:TOBYINK';
124             our $VERSION = '0.004';
125             $INC{'MooseX/Prototype/Trait/Object.pm'} = __FILE__;
126            
127             use Moose::Role;
128            
129             sub create_class { goto \&MooseX::Prototype::create_class_from_prototype };
130            
131             requires '_attribute_accessor_type';
132            
133             around new => sub {
134             my ($orig, $class, @args) = @_;
135             if (ref $class)
136             {
137             return $class->create_class->new(@args);
138             }
139             $class->$orig(@args);
140             };
141            
142             around [qw/ does DOES /] => sub {
143             my ($orig, $self, $role) = @_;
144             return 1 if $role eq -proto;
145             return $self->$orig($role);
146             };
147            
148             sub extend {
149             my $self = shift;
150             my $hash = ref($_[0]) ? $_[0] : +{@_};
151             my $extension = Moose::Meta::Class::->create(
152             $serial_name->(),
153             superclasses => [ ref $self ],
154             attributes => [
155             map { $mk_attribute->($_) }
156             grep { not /^\&/ }
157             keys %$hash
158             ],
159             methods => {
160             map { ; substr($_, 1) => $hash->{$_} }
161             grep { /^\&/ }
162             keys %$hash
163             },
164             );
165             bless $self, $extension->name;
166             if ($self->DOES('MooseX::Prototype::Trait::Object::RO'))
167             {
168             foreach my $key (keys %$hash)
169             {
170             next if $key =~ /^\&/;
171             # breaks Moose encapsulation :-(
172             $self->{$key} = $hash->{$key};
173             }
174             }
175             else
176             {
177             foreach my $key (keys %$hash)
178             {
179             next if $key =~ /^\&/;
180             $self->$key($hash->{$key});
181             }
182             }
183             return $self;
184             }
185             };
186              
187             BEGIN {
188             package MooseX::Prototype::Trait::Object::RO;
189             our $AUTHORITY = 'cpan:TOBYINK';
190             our $VERSION = '0.004';
191             $INC{'MooseX/Prototype/Trait/Object/RO.pm'} = __FILE__;
192             use Moose::Role;
193             with qw( MooseX::Prototype::Trait::Object );
194             sub _attribute_accessor_type { 'ro' };
195             };
196              
197             BEGIN {
198             package MooseX::Prototype::Trait::Object::RW;
199             our $AUTHORITY = 'cpan:TOBYINK';
200             our $VERSION = '0.004';
201             $INC{'MooseX/Prototype/Trait/Object/RW.pm'} = __FILE__;
202             use Moose::Role;
203             with qw( MooseX::Prototype::Trait::Object );
204             sub _attribute_accessor_type { 'rw' };
205             };
206              
207             1;
208              
209             __END__
210              
211             =head1 NAME
212              
213             MooseX::Prototype - prototype-based programming for Moose
214              
215             =head1 SYNOPSIS
216              
217             From Wikipedia: I<< "Prototype-based programming is a style of object-oriented
218             programming in which classes are not present, and behaviour reuse (known as
219             inheritance in class-based languages) is performed via a process of cloning
220             existing objects that serve as prototypes." >>
221              
222             use MooseX::Prototype;
223            
224             my $Person = object {
225             name => undef,
226             };
227            
228             my $Employee = $Person->new->extend({
229             job => undef,
230             employer => undef,
231             });
232            
233             my $CivilServant = $Employee->new(
234             employer => 'Government',
235             );
236            
237             $CivilServant->extend({
238             department => undef,
239             });
240            
241             my $bob = $CivilServant->new(
242             name => 'Robert',
243             department => 'HMRC',
244             job => 'Tax Inspector',
245             );
246            
247             print $bob->dump;
248            
249             # $VAR1 = bless( {
250             # name => 'Robert',
251             # job => 'Tax Inspector',
252             # department => 'HMRC',
253             # employer => 'Government'
254             # }, 'MooseX::Prototype::__ANON__::0006' );
255              
256             =head1 DESCRIPTION
257              
258             Due to familiarity with class-based languages such as Java, many
259             programmers assume that object-oriented programming is synonymous with
260             class-based programming. However, class-based programming is just one
261             kind of object-oriented programming style, and other varieties exist
262             such as role-oriented, aspect-oriented and prototype-based programming.
263              
264             A prominent example of a prototype-based programming language is
265             ECMAScript (a.k.a. Javascript/JScript/ActionScript). ECMAScript does
266             provide a thin class-like layer over the top of its prototype-based
267             OO system, which some (even experienced) ECMAScript developers rarely
268             see beyond.
269              
270             This module implements a thin prototype-like layer on top of L<Moose>'s
271             class/role-based toolkit.
272              
273             =head2 Ex-Nihilo Object Creation
274              
275             In prototype-based languages, objects are created by cloning other
276             objects. But it's often useful to be able to magic up an object out of
277             nowhere. MooseX::Prototype provides a convenience function to do this:
278              
279             =over
280              
281             =item C<< object \%attrs >>
282              
283             Creates a new object with the given attributes. The hash is treated
284             as attribute-name, attribute-value pairs, but any names beginning with
285             C<< "&" >> are installed as methods. For example:
286              
287             my $person = object {
288             "name" => "Robert",
289             "&changeName" => sub {
290             my ($self, $newname) = @_;
291             $self->name($newname);
292             },
293             };
294              
295             Objects created this way inherit from L<Moose::Object> and perform the
296             C<MooseX::Prototype::Trait::Object> role.
297              
298             =back
299              
300             =head2 Creating Objects from a Prototype
301              
302             A prototype is just an object. When you create a new object from it,
303             the prototype will be cloned and the new object will inherit all its
304             attributes and methods.
305              
306             =over
307              
308             =item C<< $prototype->new(%attrs) >>
309              
310             Creates a new object which inherits its methods and attributes from
311             C<< $prototype >>. The C<< %attrs >> hash can override attribute values
312             from the prototype, but cannot add new attributes or methods.
313              
314             This method is provided by the C<MooseX::Prototype::Trait::Object>
315             role, so C<< $prototype >> must perform that role.
316              
317             =item C<< $prototype->create_class >>
318              
319             Rather than creating a new object from a prototype, this creates a whole
320             new Moose class which can be used to instantiate objects. If you need to
321             create a whole bunch of objects from a prototype, it is probably more
322             efficient to create a class and use that, rather than just calling C<new>
323             a bunch of times.
324              
325             The class can be given a name, a la:
326              
327             $prototype->create_class("Foo::Bar");
328              
329             Otherwise an arbitary name will be generated and returned.
330              
331             This method is provided by the C<MooseX::Prototype::Trait::Object>
332             role, so C<< $prototype >> must perform that role.
333              
334             =item C<< create_class_from_prototype($prototype) >>
335              
336             A convenience function allowing you to use arbitary Moose objects (which
337             lack the C<create_class> method) as prototypes.
338              
339             Also note:
340              
341             my $obj = create_class_from_prototype($proto)->new(%attrs);
342              
343             This function is not exported by default, but can be exported using:
344              
345             use MooseX::Prototype -all;
346              
347             =back
348              
349             =head2 Extending Existing Objects
350              
351             A key feature of Javascript is that new attributes and methods can be given
352             to an object using simple assignment;
353              
354             my_object.some_attribute = 123;
355             my_object.some_method = function () { return 456 };
356              
357             In MooseX::Prototype, there is an explicit syntax for adding new attributes
358             and methods to an object.
359              
360             =over
361              
362             =item C<< $object->extend(\%attrs) >>
363              
364             As per ex-nihilo object creation, the attribute hashref can define attribute
365             name-value pairs, or new methods with a leading C<< "&" >>.
366              
367             =back
368              
369             =head1 HISTORY
370              
371             Version 0.001 of MooseX::Prototype consisted of just a single function,
372             C<use_as_prototype> which was much the same as C<create_class_from_prototype>.
373              
374             Version 0.002 is an almost complete rewrite.
375              
376             =head1 BUGS
377              
378             Please report any bugs to
379             L<http://rt.cpan.org/Dist/Display.html?Queue=MooseX-Prototype>.
380              
381             =head1 SEE ALSO
382              
383             L<Object::Prototype>,
384             L<Class::Prototyped>,
385             L<JE::Object>.
386              
387             =head1 AUTHOR
388              
389             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
390              
391             =head1 COPYRIGHT AND LICENCE
392              
393             This software is copyright (c) 2012 by Toby Inkster.
394              
395             This is free software; you can redistribute it and/or modify it under
396             the same terms as the Perl 5 programming language system itself.
397              
398             =head1 DISCLAIMER OF WARRANTIES
399              
400             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
401             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
402             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
403