From: Dave Rolsky Date: Thu, 7 Aug 2008 20:32:43 +0000 (+0000) Subject: Updated the extending recipes to use Moose::Exporter. X-Git-Tag: 0_55_01~43^2~11 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=554b76480611180fd5c368bcdce70db0b995075b;p=gitmo%2FMoose.git Updated the extending recipes to use Moose::Exporter. --- diff --git a/lib/Moose.pm b/lib/Moose.pm index d2cd2f3..03e2a95 100644 --- a/lib/Moose.pm +++ b/lib/Moose.pm @@ -713,45 +713,72 @@ to work. Here is an example: =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, C, C,...). -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, C, 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, and/or your own metaclass class instead of +L. + +The exporting needs can be asily satisfied by using +L, which is what C itself uses for +exporting. L lets you "export like Moose". + +If you define an C method in a module that uses +L, then this method will be called I +C's own C. 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 +In this example, any class that includes C will get +all of C's sugar functions, and will have their superclass +set to C. -Moose's C method supports the L form of C<{into =E $pkg}> -and C<{into_level =E 1}> +Additionally, that class can include C to unimport + +=head2 B<< Moose->init_meta(for_class => $class, base_class => $baseclass, metaclass => $metaclass) >> -=head2 B +The C method sets up the metaclass object for the class +specified by C. It also injects a a C accessor into +the class so you can get at this object. It also sets the class's +superclass to C, with L as the default. -Moose does some boot strapping: it creates a metaclass object for your class, -and then injects a C 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 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 parameter. For more detail on this topic, see L. +This method used to be documented as a function which accepted +positional parameters. This calling style will still work for +backwards compatibility. + +=head2 B + +Moose's C method supports the L form of C<{into =E $pkg}> +and C<{into_level =E 1}>. + +B: Doing this is more or less deprecated. Use L +instead, which lets you stack multiple C-alike modules +sanely. It handles getting the exported functions into the right place +for you. + =head1 CAVEATS =over 4 diff --git a/lib/Moose/Cookbook/Extending/Recipe1.pod b/lib/Moose/Cookbook/Extending/Recipe1.pod index 2be1127..8dc84aa 100644 --- a/lib/Moose/Cookbook/Extending/Recipe1.pod +++ b/lib/Moose/Cookbook/Extending/Recipe1.pod @@ -18,23 +18,13 @@ Moose::Cookbook::Extending::Recipe1 - Providing an alternate base object class 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 @@ -53,6 +43,32 @@ In this particular example, our base class issues some debugging 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. When we +call C<< Moose::Exporter->build_import_methods( also => 'Moose' ) >> +it builds an C and C method for you. The C<< also => +'Moose' >> bit says that we want to export everything that Moose does. + +The C method that gets created will call our C +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 docs for more details on its API. + +=head1 USING MyApp::UseMyBase + +To actually use our new base class, we simply use C +I of C. 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 Eautarch@urth.orgE diff --git a/lib/Moose/Cookbook/Extending/Recipe2.pod b/lib/Moose/Cookbook/Extending/Recipe2.pod index eeec31e..6f65169 100644 --- a/lib/Moose/Cookbook/Extending/Recipe2.pod +++ b/lib/Moose/Cookbook/Extending/Recipe2.pod @@ -12,50 +12,27 @@ Moose::Cookbook::Extending::Recipe2 - Acting like Moose.pm and providing sugar M 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. This recipe builds on +This recipe expands on the use of L we saw in L. Instead of providing our own object base class, we provide our own metaclass class, and we also export a sugar subroutine C. @@ -64,14 +41,14 @@ Given the above code, you can now replace all instances of C with C. Similarly, C is now replaced with C. -=head1 WARNING +The C 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>. -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 modules on CPAN already use these techniques. +See the L 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: @@ -82,8 +59,8 @@ 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 { ... } @@ -92,47 +69,6 @@ interface. Here's what it would look like in actual use: All of the normal Moose sugar (C, C, etc) is available when you C. -=head1 DISSECTION - -The first bit of magic is the call to C. What this -does is create a metaclass for the specified class. Normally, this is -called by C in its own C 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 (see L for an -example). - -The C 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 by default. The third -argument is the metaclass class, which is C by -default. - -The next bit of magic is this: - - Moose->import( { into => $caller } ); - -This use of "into" is actually part of the C API, which -C uses internally to export things like C and -C. - -Finally, we call C<< __PACKAGE__->export_to_level() >>. This method -actually comes from C. - -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 module someday. - -The C 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 the keywords given by the C argument -that were created in the C package. This functionality may -be deprecated if L begins providing it. - -Finally, we have our C subroutine. This provides a bit of -sugar that looks a lot like C. - =head1 AUTHOR Dave Rolsky Eautarch@urth.orgE diff --git a/t/000_recipes/extending/001_base_class.t b/t/000_recipes/extending/001_base_class.t new file mode 100644 index 0000000..2e305d3 --- /dev/null +++ b/t/000_recipes/extending/001_base_class.t @@ -0,0 +1,62 @@ +#!/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' ); + diff --git a/t/000_recipes/extending/002_metaclass_and_sugar.t b/t/000_recipes/extending/002_metaclass_and_sugar.t new file mode 100644 index 0000000..fc930f8 --- /dev/null +++ b/t/000_recipes/extending/002_metaclass_and_sugar.t @@ -0,0 +1,60 @@ +#!/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' );