Updated the extending recipes to use Moose::Exporter.
Dave Rolsky [Thu, 7 Aug 2008 20:32:43 +0000 (20:32 +0000)]
lib/Moose.pm
lib/Moose/Cookbook/Extending/Recipe1.pod
lib/Moose/Cookbook/Extending/Recipe2.pod
t/000_recipes/extending/001_base_class.t [new file with mode: 0644]
t/000_recipes/extending/002_metaclass_and_sugar.t [new file with mode: 0644]

index d2cd2f3..03e2a95 100644 (file)
@@ -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<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
index 2be1127..8dc84aa 100644 (file)
@@ -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<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>
index eeec31e..6f65169 100644 (file)
@@ -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<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()>.
@@ -64,14 +41,14 @@ Given the above code, you can now replace all instances of C<use
 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:
@@ -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<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>
diff --git a/t/000_recipes/extending/001_base_class.t b/t/000_recipes/extending/001_base_class.t
new file mode 100644 (file)
index 0000000..2e305d3
--- /dev/null
@@ -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 (file)
index 0000000..fc930f8
--- /dev/null
@@ -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' );