Rename Meta::Recipe6 to Meta::PrivateOrPublic_MethodMetaclass
[gitmo/Moose.git] / lib / Moose / Cookbook / Meta / PrivateOrPublic_MethodMetaclass.pod
CommitLineData
8a788638 1package Moose::Cookbook::Meta::PrivateOrPublic_MethodMetaclass;
b1d7ad19 2
daa0fd7d 3# ABSTRACT: A method metaclass for marking methods public or private
4
5__END__
b1d7ad19 6
b1d7ad19 7
daa0fd7d 8=pod
b1d7ad19 9
10=head1 SYNOPSIS
11
909c3b96 12 package MyApp::Meta::Method::PrivateOrPublic;
b1d7ad19 13
14 use Moose;
15 use Moose::Util::TypeConstraints;
16
17 extends 'Moose::Meta::Method';
18
19 has '_policy' => (
20 is => 'ro',
21 isa => enum( [ qw( public private ) ] ),
22 default => 'public',
23 init_arg => 'policy',
24 );
25
26 sub new {
27 my $class = shift;
28 my %options = @_;
29
30 my $self = $class->SUPER::wrap(%options);
31
32 $self->{_policy} = $options{policy};
33
34 $self->_add_policy_wrapper;
35
36 return $self;
37 }
38
39 sub _add_policy_wrapper {
40 my $self = shift;
41
42 return if $self->is_public;
43
44 my $name = $self->name;
45 my $package = $self->package_name;
46 my $real_body = $self->body;
47
48 my $body = sub {
49 die "The $package\::$name method is private"
50 unless ( scalar caller() ) eq $package;
51
52 goto &{$real_body};
53 };
54
55 $self->{body} = $body;
56 }
57
58 sub is_public { $_[0]->_policy eq 'public' }
59 sub is_private { $_[0]->_policy eq 'private' }
60
61 package MyApp::User;
62
63 use Moose;
64
65 has 'password' => ( is => 'rw' );
66
67 __PACKAGE__->meta()->add_method(
68 '_reset_password',
909c3b96 69 MyApp::Meta::Method::PrivateOrPublic->new(
b1d7ad19 70 name => '_reset_password',
71 package_name => __PACKAGE__,
72 body => sub { $_[0]->password('reset') },
73 policy => 'private',
74 )
75 );
76
77=head1 DESCRIPTION
78
79This example shows a custom method metaclass that models public versus
80private methods. If a method is defined as private, it adds a wrapper
81around the method which dies unless it is called from the class where
82it was defined.
83
84The way the method is added to the class is rather ugly. If we wanted
85to make this a real feature, we'd probably want to add some sort of
86sugar to allow us to declare private methods, but that is beyond the
87scope of this recipe. See the Extending recipes for more on this
88topic.
89
90The core of our custom class is the C<policy> attribute, and
91C<_add_policy_wrapper> method.
92
93You'll note that we have to explicitly set the C<policy> attribute in
94our constructor:
95
22a6230f 96 $self->{_policy} = $options{policy};
b1d7ad19 97
98That is necessary because Moose metaclasses do not use the meta API to
66d68f76 99create objects. Most Moose classes have a custom "inlined" constructor
b1d7ad19 100for speed.
101
102In this particular case, our parent class's constructor is the C<wrap>
103method. We call that to build our object, but it does not include
104subclass-specific attributes.
105
106The C<_add_policy_wrapper> method is where the real work is done. If
107the method is private, we construct a wrapper around the real
108subroutine which checks that the caller matches the package in which
109the subroutine was created.
110
111If they don't match, it dies. If they do match, the real method is
112called. We use C<goto> so that the wrapper does not show up in the
113call stack.
114
115Finally, we replace the value of C<< $self->{body} >>. This is another
116case where we have to do something a bit gross because Moose does not
117use Moose for its own implementation.
118
119When we pass this method object to the metaclass's C<add_method>
120method, it will take the method body and make it available in the
121class.
122
123Finally, when we retrieve these methods via the introspection API, we
124can call the C<is_public> and C<is_private> methods on them to get
125more information about the method.
126
127=head1 SUMMARY
128
129A custom method metaclass lets us add both behavior and
130meta-information to methods. Unfortunately, because the Perl
2a67e654 131interpreter does not provide easy hooks into method declaration, the
b1d7ad19 132API we have for adding these methods is not very pretty.
133
134That can be improved with custom Moose-like sugar, or even by using a
135tool like L<Devel::Declare> to create full-blown new keywords in Perl.
136
b1d7ad19 137=begin testing
138
139package main;
140
7c745cd9 141use Test::Fatal;
b1d7ad19 142
143my $user = MyApp::User->new( password => 'foo!' );
144
7c745cd9 145like( exception { $user->_reset_password },
b1d7ad19 146qr/The MyApp::User::_reset_password method is private/,
7c745cd9 147 '_reset_password method dies if called outside MyApp::User class');
b1d7ad19 148
149{
150 package MyApp::User;
151
152 sub run_reset { $_[0]->_reset_password }
153}
154
155$user->run_reset;
156
157is( $user->password, 'reset', 'password has been reset' );
158
159=end testing
160
161=cut