=head1 EXTENDING AND EMBEDDING MOOSE
-Moose also offers some options for extending or embedding it into your own
-framework. The basic premise is to have something that sets up your class'
-metaclass and export the moose declarators (C<has>, C<with>, C<extends>,...).
-Here is an example:
+Moose also offers some options for extending or embedding it into your
+own framework. There are several things you might want to do as part
+of such a framework. First, you probably want to export Moose's sugar
+functions (C<has>, C<extends>, etc) for users of the
+framework. Second, you may want to provide additional sugar of your
+own. Third, you may want to provide your own object base class instead
+of L<Moose::Object>, and/or your own metaclass class instead of
+L<Moose::Meta::Class>.
+
+The exporting needs can be asily satisfied by using
+L<Moose::Exporter>, which is what C<Moose.pm> itself uses for
+exporting. L<Moose::Exporter> lets you "export like Moose".
+
+If you define an C<init_meta> method in a module that uses
+L<Moose::Exporter>, then this method will be called I<before>
+C<Moose.pm>'s own C<init_meta>. This gives you a chance to provide an
+alternate object base class or metaclass class.
+
+Here is a simple example:
package MyFramework;
- use Moose;
-
- sub import {
- my $CALLER = caller();
- strict->import;
- warnings->import;
+ use strict;
+ use warnings;
- # we should never export to main
- return if $CALLER eq 'main';
- Moose::init_meta( $CALLER, 'MyFramework::Base' );
- Moose->import({into => $CALLER});
+ use Moose (); # no need to get Moose's exports
+ use Moose::Exporter;
- # Do my custom framework stuff
+ Moose::Exporter->build_import_methods( also => 'Moose' );
- return 1;
+ sub init_meta {
+ shift;
+ return Moose->init_meta( @_, base_class => 'MyFramework::Base' );
}
-=head2 B<import>
+In this example, any class that includes C<use MyFramework> will get
+all of C<Moose.pm>'s sugar functions, and will have their superclass
+set to C<MyFramework::Base>.
-Moose's C<import> method supports the L<Sub::Exporter> form of C<{into =E<gt> $pkg}>
-and C<{into_level =E<gt> 1}>
+Additionally, that class can include C<no MyFramework> to unimport
+
+=head2 B<< Moose->init_meta(for_class => $class, base_class => $baseclass, metaclass => $metaclass) >>
-=head2 B<init_meta ($class, $baseclass, $metaclass)>
+The C<init_meta> method sets up the metaclass object for the class
+specified by C<for_class>. It also injects a a C<meta> accessor into
+the class so you can get at this object. It also sets the class's
+superclass to C<base_class>, with L<Moose::Object> as the default.
-Moose does some boot strapping: it creates a metaclass object for your class,
-and then injects a C<meta> accessor into your class to retrieve it. Then it
-sets your baseclass to Moose::Object or the value you pass in unless you already
-have one. This is all done via C<init_meta> which takes the name of your class
-and optionally a baseclass and a metaclass as arguments.
+You can specify an alternate metaclass with the C<metaclass> parameter.
For more detail on this topic, see L<Moose::Cookbook::Extending::Recipe2>.
+This method used to be documented as a function which accepted
+positional parameters. This calling style will still work for
+backwards compatibility.
+
+=head2 B<import>
+
+Moose's C<import> method supports the L<Sub::Exporter> form of C<{into =E<gt> $pkg}>
+and C<{into_level =E<gt> 1}>.
+
+B<NOTE>: Doing this is more or less deprecated. Use L<Moose::Exporter>
+instead, which lets you stack multiple C<Moose.pm>-alike modules
+sanely. It handles getting the exported functions into the right place
+for you.
+
=head1 CAVEATS
=over 4
package MyApp::UseMyBase;
use Moose ();
+ use Moose::Exporter;
- sub import {
- my $caller = caller();
+ Moose::Exporter->build_import_methods( also => 'Moose' );
- return if $caller eq 'main';
-
- Moose::init_meta( $caller,
- 'MyApp::Object',
- );
-
- Moose->import( { into => $caller }, @_ );
- }
-
- sub unimport {
- my $caller = caller();
-
- Moose->unimport( { into => $caller }, @_ );
+ sub init_meta {
+ shift;
+ Moose->init_meta( @_, base_class => 'MyApp::Object' );
}
=head1 DESCRIPTION
output every time a new object is created, but you can surely think of
some more interesting things to do with your own base class.
+This all works because of the magic of L<Moose::Exporter>. When we
+call C<< Moose::Exporter->build_import_methods( also => 'Moose' ) >>
+it builds an C<import> and C<unimport> method for you. The C<< also =>
+'Moose' >> bit says that we want to export everything that Moose does.
+
+The C<import> method that gets created will call our C<init_meta>
+method, passing it C<< for_caller => $caller >> as its arguments. The
+C<$caller> is set to the class that actually imported us in the first
+place.
+
+See the L<Moose::Exporter> docs for more details on its API.
+
+=head1 USING MyApp::UseMyBase
+
+To actually use our new base class, we simply use C<MyApp::UseMyBase>
+I<instead> of C<Moose>. We get all the Moose sugar plus our new base
+class.
+
+ package Foo;
+
+ use MyApp::UseMyBase;
+
+ has 'size' => ( is => 'rw' );
+
+ no MyApp::UseMyBase;
+
=head1 AUTHOR
Dave Rolsky E<lt>autarch@urth.orgE<gt>
use strict;
use warnings;
- our @EXPORT = qw( has_table );
-
- use base 'Exporter';
- use Class::MOP;
use Moose ();
+ use Moose::Exporter;
- sub import {
- my $caller = caller();
-
- return if $caller eq 'main';
-
- Moose::init_meta(
- $caller,
- undef, # object base class
- 'MyApp::Meta::Class',
- );
-
- Moose->import( { into => $caller }, @_ );
-
- __PACKAGE__->export_to_level( 1, @_ );
- }
-
- sub unimport {
- my $caller = caller();
-
- Moose::_remove_keywords(
- source => __PACKAGE__,
- package => $caller,
- keywords => \@EXPORT,
- );
+ Moose::Exporter->build_import_methods(
+ with_caller => ['has_table'],
+ also => 'Moose',
+ );
- Moose->unimport( { into_level => 1 } );
+ sub init_meta {
+ shift;
+ Moose->init_meta( @_, metaclass => 'MyApp::Meta::Class' );
}
sub has_table {
- my $caller = caller();
-
+ my $caller = shift;
$caller->meta()->table(shift);
}
=head1 DESCRIPTION
-The code above shows what it takes to provide an import-based
-interface just like C<Moose.pm>. This recipe builds on
+This recipe expands on the use of L<Moose::Exporter> we saw in
L<Moose::Cookbook::Extending::Recipe1>. Instead of providing our own
object base class, we provide our own metaclass class, and we also
export a sugar subroutine C<has_table()>.
Moose> with C<use MyApp::Mooseish>. Similarly, C<no Moose> is now
replaced with C<no MyApp::Mooseish>.
-=head1 WARNING
+The C<with_caller> parameter specifies a list of functions that should
+be wrapped before exporting. The wrapper simply ensures that the
+importing package name is the first argument to the function, so we
+can do C<S<my $caller = shift;>>.
-This recipe covers a fairly undocumented and ugly part of Moose, and
-the techniques described here may be deprecated in a future
-release. If this happens, there will be plenty of warning, as a number
-of C<MooseX> modules on CPAN already use these techniques.
+See the L<Moose::Exporter> docs for more details on its API.
-=head1 HOW IT IS USED
+=head1 USING MyApp::Mooseish
The purpose of all this code is to provide a Moose-like
interface. Here's what it would look like in actual use:
has_table 'User';
- has 'username';
- has 'password';
+ has 'username' => ( is => 'ro' );
+ has 'password' => ( is => 'ro' );
sub login { ... }
All of the normal Moose sugar (C<has()>, C<with()>, etc) is available
when you C<use MyApp::Mooseish>.
-=head1 DISSECTION
-
-The first bit of magic is the call to C<Moose::init_meta()>. What this
-does is create a metaclass for the specified class. Normally, this is
-called by C<Moose.pm> in its own C<import()> method. However, we can
-call it first in order to provide an alternate metaclass class. We
-could also provide an alternate base object class to replace
-C<Moose::Object> (see L<Moose::Cookbook::Extending::Recipe1> for an
-example).
-
-The C<Moose::init_meta()> call takes three parameters. The first is
-the class for which we are initializing a metaclass object. The second
-is the base object, which is L<Moose::Object> by default. The third
-argument is the metaclass class, which is C<Moose::Meta::Class> by
-default.
-
-The next bit of magic is this:
-
- Moose->import( { into => $caller } );
-
-This use of "into" is actually part of the C<Sub::Exporter> API, which
-C<Moose.pm> uses internally to export things like C<has()> and
-C<extends()>.
-
-Finally, we call C<< __PACKAGE__->export_to_level() >>. This method
-actually comes from C<Exporter>.
-
-This is all a bit fragile since it doesn't stack terribly well. You
-can basically only have one Moose-alike module. This may be fixed in
-the still-notional C<MooseX::Exporter> module someday.
-
-The C<unimport()> subroutine calls the C<_remove_keywords> function
-from Moose. This function removes only the keywords exported by
-this module. More precisely, C<_remove_keywords> removes from the
-C<package> package the keywords given by the C<keywords> argument
-that were created in the C<source> package. This functionality may
-be deprecated if L<Sub::Exporter> begins providing it.
-
-Finally, we have our C<has_table()> subroutine. This provides a bit of
-sugar that looks a lot like C<has()>.
-
=head1 AUTHOR
Dave Rolsky E<lt>autarch@urth.orgE<gt>
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+use Test::Exception;
+
+BEGIN {
+ unless ( eval 'use Test::Warn; 1' ) {
+ plan skip_all => 'These tests require Test::Warn';
+ }
+ else {
+ plan tests => 4;
+ }
+}
+
+{
+ package MyApp::Base;
+ use Moose;
+
+ extends 'Moose::Object';
+
+ before 'new' => sub { warn "Making a new " . $_[0] };
+
+ no Moose;
+}
+
+{
+ package MyApp::UseMyBase;
+ use Moose ();
+ use Moose::Exporter;
+
+ Moose::Exporter->build_import_methods( also => 'Moose' );
+
+ sub init_meta {
+ shift;
+ Moose->init_meta( @_, base_class => 'MyApp::Base' );
+ }
+}
+
+{
+ package Foo;
+
+ MyApp::UseMyBase->import;
+
+ has( 'size' => ( is => 'rw' ) );
+}
+
+ok( Foo->isa('MyApp::Base'),
+ 'Foo isa MyApp::Base' );
+
+ok( Foo->can('size'),
+ 'Foo has a size method' );
+
+my $foo;
+warning_is( sub { $foo = Foo->new( size => 2 ) },
+ 'Making a new Foo',
+ 'got expected warning when calling Foo->new' );
+
+is( $foo->size(), 2, '$foo->size is 2' );
+
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 3;
+
+
+{
+ package MyApp::Meta::Class;
+ use Moose;
+
+ extends 'Moose::Meta::Class';
+
+ has 'table' => ( is => 'rw' );
+
+ no Moose;
+
+ package MyApp::Mooseish;
+
+ use strict;
+ use warnings;
+
+ use Moose ();
+ use Moose::Exporter;
+
+ Moose::Exporter->build_import_methods(
+ with_caller => ['has_table'],
+ also => 'Moose',
+ );
+
+ sub init_meta {
+ shift;
+ Moose->init_meta( @_, metaclass => 'MyApp::Meta::Class' );
+ }
+
+ sub has_table {
+ my $caller = shift;
+ $caller->meta()->table(shift);
+ }
+}
+
+{
+ package MyApp::User;
+
+ MyApp::Mooseish->import;
+
+ has_table( 'User' );
+
+ has( 'username' => ( is => 'ro' ) );
+ has( 'password' => ( is => 'ro' ) );
+
+ sub login { }
+}
+
+isa_ok( MyApp::User->meta, 'MyApp::Meta::Class' );
+is( MyApp::User->meta->table, 'User',
+ 'MyApp::User->meta->table returns User' );
+ok( MyApp::User->can('username'),
+ 'MyApp::User has username method' );