Add more tests, including some that won't pass until we can make
[gitmo/Moose.git] / lib / Moose / Util / MetaRole.pm
CommitLineData
231be3be 1package Moose::Util::MetaRole;
2
3use strict;
4use warnings;
5
6use List::MoreUtils qw( all );
7
8sub apply_metaclass_roles {
9 my %options = @_;
10
11 my $for = $options{for_class};
12
13 my $meta = _make_new_metaclass( $for, \%options );
14
15 for my $tor_class ( grep { $options{ $_ . '_roles' } }
16 qw( constructor_class destructor_class ) ) {
17
18 my $class = _make_new_class(
19 $meta->$tor_class(),
20 $options{ $tor_class . '_roles' }
21 );
22
23 $meta->$tor_class($class);
24 }
25
26 return $meta;
27}
28
29sub _make_new_metaclass {
30 my $for = shift;
31 my $options = shift;
32
33 return $for->meta()
34 unless grep { exists $options->{ $_ . '_roles' } }
35 qw(
36 metaclass
37 attribute_metaclass
38 method_metaclass
39 instance_metaclass
40 );
41
42 my $new_metaclass
43 = _make_new_class( ref $for->meta(), $options->{metaclass_roles} );
44
45 my $old_meta = $for->meta();
46
72d15b83 47 # This could get called for a Moose::Meta::Role as well as a Moose::Meta::Class
231be3be 48 my %classes = map {
49 $_ => _make_new_class( $old_meta->$_(), $options->{ $_ . '_roles' } )
72d15b83 50 }
51 grep { $old_meta->can($_) }
52 qw(
231be3be 53 attribute_metaclass
54 method_metaclass
55 instance_metaclass
56 );
57
58 return $new_metaclass->reinitialize( $for, %classes );
59}
60
61sub apply_base_class_roles {
62 my %options = @_;
63
64 my $for = $options{for_class};
65
66 my $meta = $for->meta();
67
68 my $new_base = _make_new_class(
69 $for,
70 $options{roles},
71 [ $meta->superclasses() ],
72 );
73
74 $meta->superclasses($new_base)
75 if $new_base ne $meta->name();
76}
77
78sub _make_new_class {
79 my $existing_class = shift;
80 my $roles = shift;
81 my $superclasses = shift || [$existing_class];
82
83 return $existing_class unless $roles;
84
85 my $meta = $existing_class->meta();
86
87 return $existing_class
88 if $meta->can('does_role') && all { $meta->does_role($_) } @{$roles};
89
90 return Moose::Meta::Class->create_anon_class(
91 superclasses => $superclasses,
92 roles => $roles,
93 cache => 1,
94 )->name();
95}
96
971;
c59bc009 98
99__END__
100
101=head1 NAME
102
103Moose::Util::MetaRole - Apply roles to any metaclass, as well as the object base class
104
105=head1 SYNOPSIS
106
107 package MyApp::Moose;
108
109 use strict;
110 use warnings;
111
112 use Moose ();
113 use Moose::Exporter;
114 use Moose::Util::Meta::Role;
115
116 use MyApp::Role::Meta::Class;
117 use MyApp::Role::Meta::Method::Constructor;
118 use MyApp::Role::Object;
119
120 Moose::Exporter->setup_import_methods( also => 'Moose' );
121
122 sub init_meta {
123 shift;
124 my %options = @_;
125
126 Moose->init_meta(%options);
127
128 Moose::Util::MetaRole::apply_metaclass_roles(
129 for_class => $options{for_class},
130 metaclass_roles => ['MyApp::Role::Meta::Class'],
131 constructor_class_roles => ['MyApp::Role::Meta::Method::Constructor'],
132 );
133
134 Moose::Util::MetaRole::apply_base_class_roles(
135 for_class => $options{for_class},
136 roles => ['MyApp::Role::Object'],
137 );
138
139 return $options{for_class}->meta();
140 }
141
142=head1 DESCRIPTION
143
dd9a6d87 144B<The whole concept behind this module is still considered
145experimental, and it could go away in the future!>
146
c59bc009 147This utility module is designed to help authors of Moose extensions
148write extensions that are able to cooperate with other Moose
149extensions. To do this, you must write your extensions as roles, which
150can then be dynamically applyied to the caller's metaclasses.
151
152This module makes sure to preserve any existing superclasses and roles
153already set for the meta objects, which means that any number of
154extensions can apply roles in any order.
155
156=head1 USAGE
157
158B<It is very important that you only call this module's functions when
159your module is imported by the caller>. The process of applying roles
160to the metaclass reinitializes the metaclass object, which wipes out
161any existing attributes already defined. However, as long as you do
162this when your module is imported, the caller should not have any
163attributes defined yet.
164
165The easiest way to ensure that this happens is to use
166L<Moose::Exporter> and provide an C<init_meta> method that will be
167called when imported.
168
169=head1 FUNCTIONS
170
171This module provides two functions.
172
173=head2 apply_metaclass_roles( ... )
174
175This function will apply roles to one or more metaclasses for the
176specified class. It accepts the following parameters:
177
178=over 4
179
180=item * for_class => $name
181
182This specifies the class for which to alter the meta classes.
183
184=item * metaclass_roles => \@roles
185
186=item * attribute_metaclass_roles => \@roles
187
188=item * method_metaclass_roles => \@roles
189
190=item * instance_metaclass_roles => \@roles
191
192=item * constructor_class_roles => \@roles
193
194=item * destructor_class_roles => \@roles
195
196These parameter all specify one or more roles to be applied to the
197specified metaclass. You can pass any or all of these parameters at
198once.
199
200=back
201
202=head2 apply_base_class_roles( for_class => $class, roles => \@roles )
203
204This function will apply the specified roles to the object's base class.
205
206=head1 AUTHOR
207
208Dave Rolsky E<lt>autarch@urth.orgE<gt>
209
210=head1 COPYRIGHT AND LICENSE
211
212Copyright 2008 by Infinity Interactive, Inc.
213
214L<http://www.iinteractive.com>
215
216This library is free software; you can redistribute it and/or modify
217it under the same terms as Perl itself.
218
219=cut