Merge trunk to meta-role-helper branch
Dave Rolsky [Fri, 29 Aug 2008 15:59:16 +0000 (15:59 +0000)]
14 files changed:
Changes
lib/Moose/Cookbook.pod
lib/Moose/Cookbook/Extending/Recipe1.pod
lib/Moose/Cookbook/Extending/Recipe2.pod
lib/Moose/Cookbook/Extending/Recipe3.pod [new file with mode: 0644]
lib/Moose/Cookbook/Extending/Recipe4.pod [new file with mode: 0644]
lib/Moose/Exporter.pm
lib/Moose/Meta/Class.pm
lib/Moose/Meta/Role/Application/ToMetaclassInstance.pm [deleted file]
lib/Moose/Role.pm
lib/Moose/Util.pm
lib/Moose/Util/MetaRole.pm [new file with mode: 0644]
t/050_metaclasses/015_metarole.t [new file with mode: 0644]
t/050_metaclasses/016_metarole_w_metaclass_pm.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index 19e44fc..737db04 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,9 +1,14 @@
 Revision history for Perl extension Moose
 
-0.56
+0.55_02
     * Makefile.PL and Moose.pm
       - explicitly require Perl 5.8.0+ (Dave Rolsky)
 
+    * Moose::Util::MetaRole
+      - This simplifies the application of roles to any meta class, as
+        well as the base object class. Reimplemented metaclass traits
+        using this module. (Dave Rolsky)
+
     * Moose::Util::TypeConstraints
       - Fix warnings from find_type_constraint if the type is not 
         found (t0m).
index 3f338e2..5a9d225 100644 (file)
@@ -169,14 +169,27 @@ if you plan to write your own C<MooseX> module.
 
 =over 4
 
-=item L<Moose::Cookbook::Extending::Recipe1> - Providing an alternate base object class
+=item L<Moose::Cookbook::Extending::Recipe1> - Moose extension overview
+
+There are quite a number of ways to extend Moose. This recipe explains
+provides an overview of each method, and provides recommendations for
+when each is appropriate.
+
+=item L<Moose::Cookbook::Extending::Recipe2> - Providing a base object class role
+
+Many base object class extensions can be implemented as roles. This
+example shows how to provide a base object class debugging role that
+is applied to any class that uses a notional C<MooseX::Debugging>
+module.
+
+=item L<Moose::Cookbook::Extending::Recipe3> - Providing an alternate base object class
 
 You may find that you want to provide an alternate base object class
 along with a meta extension, or maybe you just want to add some
 functionality to all your classes without typing C<extends
 'MyApp::Base'> over and over.
 
-=item L<Moose::Cookbook::Extending::Recipe2> - Acting like Moose.pm and providing sugar Moose-style
+=item L<Moose::Cookbook::Extending::Recipe4> - Acting like Moose.pm and providing sugar Moose-style
 
 This recipe shows how to provide a replacement for C<Moose.pm>. You
 may want to do this as part of the API for a C<MooseX> module,
index 47f89f9..25380f0 100644 (file)
 
 =head1 NAME
 
-Moose::Cookbook::Extending::Recipe1 - Providing an alternate base object class
+Moose::Cookbook::Extending::Recipe - Moose extension overview
 
-=head1 SYNOPSIS
+=head1 DESCRIPTION
+
+Moose has quite a number of ways in which extensions can hook into
+Moose and change its behavior. Moose also has a lot of behavior that
+can be changed. This recipe will provide an overview of each extension
+method and give you some recommendations on what tools to use.
+
+If you haven't yet read the recipes on metaclasses, go read those
+first. You can't really write Moose extensions without understanding
+the metaclasses, and those recipes also demonstrate some basic
+extensions mechanisms such as metaclass subclasses and traits.
+
+=head2 Playing Nice With Others
+
+One of the goals of this overview is to help you build extensions that
+cooperate well with other extensions. This is especially important if
+you plan to release your extension to CPAN.
+
+Moose comes with several modules that exist to help your write
+cooperative extensions. These are L<Moose::Exporter> and
+L<Moose::Util::MetaRole>. By using these two modules to implement your
+extensions, you will ensure that your extension works with both the
+Moose core features and any other CPAN extension using those modules.
+
+=head1 PARTS OF Moose YOU CAN EXTEND
+
+The types of things you might want to do in Moose extensions broadly
+fall into a few categories.
+
+=head2 Metaclass Extensions
+
+One way of extending Moose is by extending one or more Moose
+metaclasses. For example, in L<Moose::Cookbook::Meta::Recipe4> we saw
+a metaclass subclass that added a C<table> attribute to the
+metaclass. If you were writing an ORM, this would be a logical
+extension.
+
+Many of the Moose extensions on CPAN work by providing an attribute
+metaclass extension. For example, the C<MooseX::AttributeHelpers>
+distro provides a new attribute metaclass that lets you delegate
+behavior to a non-object attribute (a hashref or simple number).
+
+A metaclass extension can be packaged as a subclass or a
+role/trait. If you can, we recommend using traits instead of
+subclasses, since it's generally much easier to combine disparate
+traits then it is to combine a bunch of subclasses.
+
+When your extensions are implemented as roles, you can apply them with
+the L<Moose::Util::MetaRole> module.
+
+=head2 Providing Sugar Subs
+
+As part of a metaclass extension, you may also want to provide some
+sugar subroutines, much like C<Moose.pm> does. Moose provides a helper
+module called L<Moose::Exporter> that makes this much simpler. This
+will be used in several of the extension recipes.
+
+=head2 Object Class Extensions
+
+Another common Moose extension is to change the default object class
+behavior. For example, the C<MooseX::Singleton> extension changes the
+behavior of your objects so that they are singletons. The
+C<MooseX::StrictConstructor> extension makes the constructor reject
+arguments which don't match its attributes.
+
+Object class extensions often also include metaclass extensions. In
+particular, if you want your object extension to work when a class is
+made immutable, you may need to extend some or all of the
+C<Moose::Meta::Instance>, C<Moose::Meta::Method::Constructor>, and
+C<Moose::Meta::Method::Destructor> objects.
+
+The L<Moose::Util::MetaRole> module lets you apply roles to the base
+object class, as well as the meta classes just mentioned.
 
-  package MyApp::Base;
-  use Moose;
+=head2 Providing a Role
 
-  extends 'Moose::Object';
+Some extensions come in the form of a role for you to consume. The
+C<MooseX::Object::Pluggable> extension is a great example of this. In
+fact, despite the C<MooseX> name, it does not actually change anything
+about Moose's behavior. Instead, it is just a role that an object
+which wants to be pluggable can consume.
+
+If you are implementing this sort of extension, you don't need to do
+anything special. You simply create a role and document that it should
+be used via the normal C<with> sugar:
+
+   package RoleConsumer;
+
+   use Moose;
+
+   with 'MooseX::My::Role';
+
+=head2 New Types
+
+Another common Moose extension is a new type for the Moose type
+system. In this case, you simply create a type in your module. When
+people load your module, the type is created, and they can refer to it
+by name after that. The C<MooseX::Types::URI> and
+C<MooseX::Types::DateTime> distros are two good examples of how this
+works.
+
+=head1 ROLES VS TRAITS VS SUBCLASSES
+
+It is important to understand that B<roles and traits are the same
+thing>. A role can be used as a trait, and a trait is a role. The only
+thing that distinguishes the two is that a trait is packaged in a way
+that lets Moose resolve a short name to a class name. In other words,
+with a trait, the caller can specify it by a short name like "Big",
+and Moose will resolve it to a class like
+C<MooseX::Embiggen::Meta::Attribute::Role::Big>.
+
+See L<Moose::Cookbook::Meta::Recipe3> and
+L<Moose::Cookbook::Meta::Recipe5> for examples of traits in action. In
+particular, both of these recipes demonstrate the trait resolution
+mechanism.
+
+Implementing an extension as a (set of) metaclass or base object
+role(s) will make your extension more cooperative. It is hard for an
+end-user to effectively combine together multiple metaclass
+subclasses, but it can be very easy to combine roles.
+
+=head1 USING YOUR EXTENSION
+
+There are a number of ways in which an extension can be applied. In
+some cases you can provide multiple ways of consuming your extension.
+
+=head2 Extensions as Metaclass Traits
+
+If your extension is available as a trait, you can ask end users to
+simply specify it in a list of traits. Currently, this only works for
+metaclass and attribute metaclass traits:
+
+  use Moose -traits => [ 'Big', 'Blue' ];
+
+  has 'animal' =>
+     ( traits => [ 'Big', 'Blue' ],
+       ...
+     );
+
+If your extension applies to any other metaclass, or the object base
+class, you cannot use the trait mechanism.
+
+The benefit of the trait mechanism is that is very easy to see where a
+trait is applied in the code, and consumers have fine-grained control
+over what the trait applies to. This is especially true for attribute
+traits, where you can apply the trait to just one attribute in a
+class.
 
-  before 'new' => sub { warn "Making a new " . $_[0] };
+=head2 Extensions as Metaclass (and Base Object) Subclasses
 
-  no Moose;
+Moose does not provide any simple APIs for consumers to use a subclass
+extension, excep for attribute metaclasses. The attribute declaration
+parameters include a C<metaclass> parameter a consumer of your
+extension can use to specify your subclass.
+
+This is one reason why implementing an extension as a subclass can be
+a poor choice. However, you can force the use of certain subclasses at
+import time by calling C<< Moose->init_meta >> for the caller, and
+providing an alternate metaclass or base object class.
+
+If you do want to do this, you should look at using C<Moose::Exporter>
+to re-export the C<Moose.pm> sugar subroutines. When you use
+L<Moose::Exporter> and your exporting class has an C<init_meta>
+method, L<Moose::Exporter> makes sure that this C<init_meta> method
+gets called when your class is imported.
+
+Then in your C<init_meta> you can arrange for the caller to use your
+subclasses:
+
+  package MooseX::Embiggen;
 
-  package MyApp::UseMyBase;
   use Moose ();
   use Moose::Exporter;
 
+  use MooseX::Embiggen::Meta::Class;
+  use MooseX::Embiggen::Object;
+
   Moose::Exporter->setup_import_methods( also => 'Moose' );
 
   sub init_meta {
-      shift;
-      Moose->init_meta( @_, base_class => 'MyApp::Object' );
+      shift; # just your package name
+      my %options = @_;
+
+      return Moose->init_meta(
+          for_class  => $options{for_class},
+          metaclass  => 'MooseX::Embiggen::Meta::Class',
+          base_class => 'MooseX::Embiggen::Object',
+      );
   }
 
-=head1 DESCRIPTION
+=head2 Extensions as Metaclass (and Base Object) Roles
 
-Often you find that you want to share some behavior between all your
-classes. One way to do that is to make a base class and simply add
-C<S<extends 'MyApp::Base'>> to every class in your
-application. However, that can get tedious. Instead, you can simply
-create your Moose-alike module that sets the base object class to
-C<MyApp::Base> for you.
+Implementing your extensions as metaclass roles makes your extensions
+easy to apply, and cooperative with other metaclass role-based extensions.
 
-Then, instead of writing C<S<use Moose>> you can write C<S<use
-MyApp::UseMyBase>>.
+Just as with a subclass, you will probably want to package your
+extensions for consumption with a single module that uses
+L<Moose::Exporter>. However, in this case, you will use
+L<Moose::Util::MetaRole> to apply all of your roles. The advantage of
+using this module is that I<it preserves any subclassing or roles
+already applied to the users metaclasses>. This means that your
+extension is cooperative I<by default>, and consumers of your
+extension can easily use it with other role-based extensions.
 
-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.
+  package MooseX::Embiggen;
 
-This all works because of the magic of L<Moose::Exporter>. When we
-call C<< Moose::Exporter->setup_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.
+  use Moose ();
+  use Moose::Exporter;
+  use Moose::Util::MetaRole;
 
-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.
+  use MooseX::Embiggen::Role::Meta::Class;
+  use MooseX::Embiggen::Role::Meta::Attribute;
+  use MooseX::Embiggen::Role::Meta::Method::Constructor
+  use MooseX::Embiggen::Role::Object;
 
-See the L<Moose::Exporter> docs for more details on its API.
+  Moose::Exporter->setup_import_methods( also => 'Moose' );
 
-=head1 USING MyApp::UseMyBase
+  sub init_meta {
+      shift; # just your package name
+      my %options = @_;
 
-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.
+      Moose->init_meta(%options);
+
+      my $meta = Moose::Util::MetaRole::apply_metaclass_roles(
+          for_class       => $options{for_class},
+          metaclass_roles => ['MooseX::Embiggen::Role::Meta::Class'],
+          attribute_metaclass_roles =>
+              ['MooseX::Embiggen::Role::Meta::Attribute'],
+          constructor_class_roles =>
+              ['MooseX::Embiggen::Role::Meta::Method::Constructor'],
+      );
+
+      Moose::Util::MetaRole::apply_base_class_roles(
+          for_class => $options{for_class},
+          roles     => ['MooseX::Embiggen::Role::Object'],
+      );
+
+      return $meta;
+  }
+
+As you can see from this example, you can use C<Moose::Util::MetaRole>
+to apply roles to any metaclass, as well as the base object class. If
+some other extension has already applied its own roles, they will be
+preserved when your extension applies its roles, and vice versa.
+
+=head2 Providing Sugar
+
+With L<Moose::Exporter>, you can also export your own sugar subs, as
+well as those from other sugar modules:
+
+  package MooseX::Embiggen;
+
+  use Moose ();
+  use Moose::Exporter;
+
+  Moose::Exporter->setup_import_methods(
+      with_caller => ['embiggen'],
+      also        => 'Moose',
+  );
+
+  sub init_meta { ... }
+
+  sub embiggen {
+      my $caller = shift;
+      $caller->meta()->embiggen(@_);
+  }
+
+And then the consumer of your extension can use your C<embiggen> sub:
+
+  package Consumer;
+
+  use MooseX::Embiggen;
+
+  extends 'Thing';
+
+  embiggen ...;
+
+This can be combined with metaclass and base class roles quite easily.
+
+=head1 LEGACY EXTENSION METHODOLOGIES
+
+Before the existence of L<Moose::Exporter> and
+L<Moose::Util::MetaRole>, there were a number of other ways to extend
+Moose. In general, these methods were less cooperative, and only
+worked well with a single extension.
+
+These methods include C<metaclass.pm>, C<Moose::Policy> (which uses
+C<metaclass.pm> under the hood), and various hacks to do what
+L<Moose::Exporter> does. Please do not use these for your own
+extensions.
+
+Note that if you write a cooperative extension, it should cooperate
+with older extensions, though older extensions generally do not
+cooperate with each oether.
 
-  package Foo;
+=head1 CONCLUSION
 
-  use MyApp::UseMyBase;
+If you can write your extension as one or more metaclass and base
+object roles, please consider doing so. Make sure to read the docs for
+L<Moose::Exporter> and L<Moose::Util::MetaRole> as well.
 
-  has 'size' => ( is => 'rw' );
+=head2 Caveat
 
-  no MyApp::UseMyBase;
+The L<Moose::Util::MetaRole> API is still considered an experiment,
+and could go away or change in the future.
 
 =head1 AUTHOR
 
@@ -75,7 +317,7 @@ Dave Rolsky E<lt>autarch@urth.orgE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2006-2008 by Infinity Interactive, Inc.
+Copyright 2008 by Infinity Interactive, Inc.
 
 L<http://www.iinteractive.com>
 
index 98a2ea7..0bdeae5 100644 (file)
@@ -3,71 +3,53 @@
 
 =head1 NAME
 
-Moose::Cookbook::Extending::Recipe2 - Acting like Moose.pm and providing sugar Moose-style
+Moose::Cookbook::Extending::Recipe2 - Providing a role for the base object class
 
 =head1 SYNOPSIS
 
-  package MyApp::Mooseish;
+  package MooseX::Debugging;
 
   use strict;
   use warnings;
 
-  use Moose ();
   use Moose::Exporter;
+  use Moose::Util::MetaRole;
+  use MooseX::Debugging::Role::Object;
 
-  Moose::Exporter->setup_import_methods(
-      with_caller => ['has_table'],
-      also        => 'Moose',
-  );
+  Moose::Exporter->setup_import_methods();
 
   sub init_meta {
       shift;
-      Moose->init_meta( @_, metaclass => 'MyApp::Meta::Class' );
-  }
+      my %options = @_;
 
-  sub has_table {
-      my $caller = shift;
-      $caller->meta()->table(shift);
+      Moose::Util::MetaRole::apply_base_object_roles(
+          for_class => $options{for_class},
+          role      => ['MooseX::Debugging::Role::Object'],
+      );
   }
 
-=head1 DESCRIPTION
-
-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()>.
-
-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>.
-
-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;>>.
-
-See the L<Moose::Exporter> docs for more details on its API.
 
-=head1 USING MyApp::Mooseish
+  package MooseX::Debugging::Role::Object;
 
-The purpose of all this code is to provide a Moose-like
-interface. Here's what it would look like in actual use:
+  after 'BUILD' => sub {
+      my $self = shift;
 
-  package MyApp::User;
-
-  use MyApp::Mooseish;
-
-  has_table 'User';
+      warn "Made a new " . ref $self . " object\n";
+  }
 
-  has 'username' => ( is => 'ro' );
-  has 'password' => ( is => 'ro' );
+=head1 DESCRIPTION
 
-  sub login { ... }
+In this example, we provide a role for the base object class that adds
+some simple debugging output. Every time an object is created, it
+spits out a warning saying what type of object it was.
 
-  no MyApp::Mooseish;
+Obviously, a real debugging role would do something more interesting,
+but this recipe is all about how we apply that role.
 
-All of the normal Moose sugar (C<has()>, C<with()>, etc) is available
-when you C<use MyApp::Mooseish>.
+In this case, with the combination of L<Moose::Exporter> and
+L<Moose::Util::MetaRole>, we ensure that when a module does "S<use
+MooseX::Debugging>", it automatically gets the debugging role applied
+to its base object class.
 
 =head1 AUTHOR
 
@@ -75,11 +57,12 @@ Dave Rolsky E<lt>autarch@urth.orgE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
-Copyright 2006-2008 by Infinity Interactive, Inc.
+Copyright 2008 by Infinity Interactive, Inc.
 
 L<http://www.iinteractive.com>
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself.
 
-=pod
+=cut
+
diff --git a/lib/Moose/Cookbook/Extending/Recipe3.pod b/lib/Moose/Cookbook/Extending/Recipe3.pod
new file mode 100644 (file)
index 0000000..3abe740
--- /dev/null
@@ -0,0 +1,85 @@
+
+=pod
+
+=head1 NAME
+
+Moose::Cookbook::Extending::Recipe3 - Providing an alternate base object class
+
+=head1 SYNOPSIS
+
+  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->setup_import_methods( also => 'Moose' );
+
+  sub init_meta {
+      shift;
+      Moose->init_meta( @_, base_class => 'MyApp::Object' );
+  }
+
+=head1 DESCRIPTION
+
+Often you find that you want to share some behavior between all your
+classes. One way to do that is to make a base class and simply add
+C<S<extends 'MyApp::Base'>> to every class in your
+application. However, that can get tedious. Instead, you can simply
+create your Moose-alike module that sets the base object class to
+C<MyApp::Base> for you.
+
+Then, instead of writing C<S<use Moose>> you can write C<S<use
+MyApp::UseMyBase>>.
+
+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->setup_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>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2006-2008 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/lib/Moose/Cookbook/Extending/Recipe4.pod b/lib/Moose/Cookbook/Extending/Recipe4.pod
new file mode 100644 (file)
index 0000000..a6b9c3f
--- /dev/null
@@ -0,0 +1,85 @@
+
+=pod
+
+=head1 NAME
+
+Moose::Cookbook::Extending::Recipe4 - Acting like Moose.pm and providing sugar Moose-style
+
+=head1 SYNOPSIS
+
+  package MyApp::Mooseish;
+
+  use strict;
+  use warnings;
+
+  use Moose ();
+  use Moose::Exporter;
+
+  Moose::Exporter->setup_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);
+  }
+
+=head1 DESCRIPTION
+
+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()>.
+
+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>.
+
+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;>>.
+
+See the L<Moose::Exporter> docs for more details on its API.
+
+=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:
+
+  package MyApp::User;
+
+  use MyApp::Mooseish;
+
+  has_table 'User';
+
+  has 'username' => ( is => 'ro' );
+  has 'password' => ( is => 'ro' );
+
+  sub login { ... }
+
+  no MyApp::Mooseish;
+
+All of the normal Moose sugar (C<has()>, C<with()>, etc) is available
+when you C<use MyApp::Mooseish>.
+
+=head1 AUTHOR
+
+Dave Rolsky E<lt>autarch@urth.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2006-2008 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=pod
index 9d4d64c..fe3bc4a 100644 (file)
@@ -6,6 +6,7 @@ use warnings;
 use Carp qw( confess );
 use Class::MOP;
 use List::MoreUtils qw( first_index uniq );
+use Moose::Util::MetaRole;
 use Sub::Exporter;
 
 
@@ -215,10 +216,10 @@ sub _make_sub_exporter_params {
                 $did_init_meta = 1;
             }
 
-            if ($did_init_meta) {
+            if ( $did_init_meta && @{$traits} ) {
                 _apply_meta_traits( $CALLER, $traits );
             }
-            elsif ( $traits && @{$traits} ) {
+            elsif ( @{$traits} ) {
                 confess
                     "Cannot provide traits when $class does not have an init_meta() method";
             }
@@ -231,7 +232,7 @@ sub _make_sub_exporter_params {
 sub _strip_traits {
     my $idx = first_index { $_ eq '-traits' } @_;
 
-    return ( undef, @_ ) unless $idx >= 0 && $#_ >= $idx + 1;
+    return ( [], @_ ) unless $idx >= 0 && $#_ >= $idx + 1;
 
     my $traits = $_[ $idx + 1 ];
 
@@ -245,8 +246,7 @@ sub _strip_traits {
 sub _apply_meta_traits {
     my ( $class, $traits ) = @_;
 
-    return
-        unless $traits && @$traits;
+    return unless @{$traits};
 
     my $meta = $class->meta();
 
@@ -255,21 +255,16 @@ sub _apply_meta_traits {
         'Cannot determine metaclass type for trait application . Meta isa '
         . ref $meta;
 
-    # We can only call does_role() on Moose::Meta::Class objects, and
-    # we can only do that on $meta->meta() if it has already had at
-    # least one trait applied to it. By default $meta->meta() returns
-    # a Class::MOP::Class object (not a Moose::Meta::Class).
-    my @traits = grep {
-        $meta->meta()->can('does_role')
-            ? not $meta->meta()->does_role($_)
-            : 1
-        }
-        map { Moose::Util::resolve_metatrait_alias( $type => $_ ) } @$traits;
+    my @resolved_traits
+        = map { Moose::Util::resolve_metatrait_alias( $type => $_ ) }
+        @$traits;
 
-    return unless @traits;
+    return unless @resolved_traits;
 
-    Moose::Util::apply_all_roles_with_method( $meta,
-        'apply_to_metaclass_instance', \@traits );
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class       => $class,
+        metaclass_roles => \@resolved_traits,
+    );
 }
 
 sub _get_caller {
index 42e842d..43b74f6 100644 (file)
@@ -23,6 +23,16 @@ __PACKAGE__->meta->add_attribute('roles' => (
     default => sub { [] }
 ));
 
+__PACKAGE__->meta->add_attribute('constructor_class' => (
+    accessor => 'constructor_class',
+    default  => sub { 'Moose::Meta::Method::Constructor' }
+));
+
+__PACKAGE__->meta->add_attribute('destructor_class' => (
+    accessor => 'destructor_class',
+    default  => sub { 'Moose::Meta::Method::Destructor' }
+));
+
 sub initialize {
     my $class = shift;
     my $pkg   = shift;
@@ -283,52 +293,71 @@ sub _find_next_method_by_name_which_is_not_overridden {
     return undef;
 }
 
+# Right now, this method does not handle the case where two
+# metaclasses differ only in roles applied against a common parent
+# class. This can happen fairly easily when ClassA applies metaclass
+# Role1, and then a subclass, ClassB, applies a metaclass Role2. In
+# reality, the way to resolve the problem is to apply Role1 to
+# ClassB's metaclass. However, we cannot currently detect this, and so
+# we simply fail to fix the incompatibility.
+#
+# The algorithm for fixing it is not that complicated.
+#
+# First, we see if the two metaclasses share a common parent (probably
+# Moose::Meta::Class).
+#
+# Second, we see if the metaclasses only differ in terms of roles
+# applied. This second point is where things break down. There is no
+# easy way to determine if the difference is from roles only. To do
+# that, we'd need to able to reliably determine the origin of each
+# method and attribute in each metaclass. If all the unshared methods
+# & attributes come from roles, and there is no name collision, then
+# we can apply the missing roles to the child's metaclass.
+#
+# Tracking the origin of these things will require some fairly
+# invasive changes to various parts of Moose & Class::MOP.
+#
+# For now, the workaround is for ClassB to subclass ClassA _and then_
+# apply metaclass roles to its metaclass.
 sub _fix_metaclass_incompatability {
     my ($self, @superclasses) = @_;
+
     foreach my $super (@superclasses) {
         # don't bother if it does not have a meta.
-        my $meta = Class::MOP::Class->initialize($super) or next;
-        next unless $meta->isa("Class::MOP::Class");
+        my $super_meta = Class::MOP::Class->initialize($super) or next;
+        next unless $super_meta->isa("Class::MOP::Class");
 
         # get the name, make sure we take
         # immutable classes into account
-        my $super_meta_name = ($meta->is_immutable
-            ? $meta->get_mutable_metaclass_name
-            : ref($meta));
+        my $super_meta_name
+            = $super_meta->is_immutable
+            ? $super_meta->get_mutable_metaclass_name
+            : ref($super_meta);
 
-        # but if we have anything else,
-        # we need to check it out ...
-        unless (# see if of our metaclass is incompatible
+        next if
+            # if our metaclass is compatible
             $self->isa($super_meta_name)
                 and
-            # and see if our instance metaclass is incompatible
-            $self->instance_metaclass->isa($meta->instance_metaclass)
-        ) {
-            if ( $meta->isa(ref($self)) ) {
-                unless ( $self->is_pristine ) {
-                    confess "Not reinitializing metaclass for " . $self->name . ", it isn't pristine";
-                }
-                # also check values %{ $self->get_method_map } for any generated methods
-
-                # NOTE:
-                # We might want to consider actually
-                # transfering any attributes from the
-                # original meta into this one, but in
-                # general you should not have any there
-                # at this point anyway, so it's very
-                # much an obscure edge case anyway
-                $self = $meta->reinitialize(
-                    $self->name,
-                    attribute_metaclass => $meta->attribute_metaclass,
-                    method_metaclass    => $meta->method_metaclass,
-                    instance_metaclass  => $meta->instance_metaclass,
-                );
-            } else {
-                # this will be called soon enough, for now we let it slide
-                # $self->check_metaclass_compatability()
-            }
+            # and our instance metaclass is also compatible then no
+            # fixes are needed
+            $self->instance_metaclass->isa( $super_meta->instance_metaclass );
+
+        next unless $super_meta->isa( ref($self) );
+
+        unless ( $self->is_pristine ) {
+            confess "Not reinitializing metaclass for "
+                . $self->name
+                . ", it isn't pristine";
         }
+
+        $self = $super_meta->reinitialize(
+            $self->name,
+            attribute_metaclass => $super_meta->attribute_metaclass,
+            method_metaclass    => $super_meta->method_metaclass,
+            instance_metaclass  => $super_meta->instance_metaclass,
+        );
     }
+
     return $self;
 }
 
@@ -424,8 +453,8 @@ sub make_immutable {
     my $self = shift;
     $self->SUPER::make_immutable
       (
-       constructor_class => 'Moose::Meta::Method::Constructor',
-       destructor_class  => 'Moose::Meta::Method::Destructor',
+       constructor_class => $self->constructor_class,
+       destructor_class  => $self->destructor_class,
        inline_destructor => 1,
        # NOTE:
        # no need to do this,
diff --git a/lib/Moose/Meta/Role/Application/ToMetaclassInstance.pm b/lib/Moose/Meta/Role/Application/ToMetaclassInstance.pm
deleted file mode 100644 (file)
index d8bfe39..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-package Moose::Meta::Role::Application::ToMetaclassInstance;
-
-use strict;
-use warnings;
-use metaclass;
-
-use Scalar::Util 'blessed';
-
-our $VERSION   = '0.55_01';
-$VERSION = eval $VERSION;
-our $AUTHORITY = 'cpan:STEVAN';
-
-use base 'Moose::Meta::Role::Application::ToClass';
-
-__PACKAGE__->meta->add_attribute('rebless_params' => (
-    reader  => 'rebless_params',
-    default => sub { {} }
-));
-
-my %ANON_CLASSES;
-
-sub apply {
-    my ( $self, $role, $meta ) = @_;
-
-    my $anon_role_key = (blessed($meta) . $role->name);
-
-    my $class;
-    if (exists $ANON_CLASSES{$anon_role_key} && defined $ANON_CLASSES{$anon_role_key}) {
-        $class = $ANON_CLASSES{$anon_role_key};
-    }
-    else {
-        my $metaclass_class
-            = ( ref $meta )->can('create_anon_class')
-            ? ref $meta
-            : 'Moose::Meta::Class';
-        $class = $metaclass_class->create_anon_class(
-            superclasses => [ blessed($meta) ],
-        );
-
-        $ANON_CLASSES{$anon_role_key} = $class;
-        $self->SUPER::apply( $role, $class );
-    }
-
-    $class->rebless_instance( $meta, %{ $self->rebless_params } );
-}
-
-1;
-
-__END__
-
-=pod
-
-=head1 NAME
-
-Moose::Meta::Role::Application::ToMetaclassInstance - Compose a role into a metaclass instance
-
-=head1 DESCRIPTION
-
-=head2 METHODS
-
-=over 4
-
-=item B<new>
-
-=item B<meta>
-
-=item B<apply>
-
-=item B<rebless_params>
-
-=back
-
-=head1 BUGS
-
-All complex software has bugs lurking in it, and this module is no
-exception. If you find a bug please either email me, or add the bug
-to cpan-RT.
-
-=head1 AUTHOR
-
-Stevan Little E<lt>stevan@iinteractive.comE<gt>
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright 2006-2008 by Infinity Interactive, Inc.
-
-L<http://www.iinteractive.com>
-
-This library is free software; you can redistribute it and/or modify
-it under the same terms as Perl itself.
-
-=cut
-
index 318fade..47ff324 100644 (file)
@@ -140,7 +140,13 @@ sub init_meta {
     }
     else {
         $meta = $metaclass->initialize($role);
-        $meta->alias_method('meta' => sub { $meta });
+
+        $meta->add_method(
+            'meta' => sub {
+                # re-initialize so it inherits properly
+                $metaclass->initialize( ref($_[0]) || $_[0] );
+            }
+        );
     }
 
     return $meta;
index b5bed65..91e8745 100644 (file)
@@ -73,16 +73,9 @@ sub search_class_by_role {
 sub apply_all_roles {
     my $applicant = shift;
 
-    apply_all_roles_with_method( $applicant, 'apply', [@_] );
-}
-
-sub apply_all_roles_with_method {
-    my ( $applicant, $apply_method, $role_list ) = @_;
-
-    confess "Must specify at least one role to apply to $applicant"
-        unless @$role_list;
+    confess "Must specify at least one role to apply to $applicant" unless @_;
 
-    my $roles = Data::OptList::mkopt($role_list);
+    my $roles = Data::OptList::mkopt( [@_] );
 
     my $meta = ( blessed $applicant ? $applicant : find_meta($applicant) );
 
@@ -98,11 +91,10 @@ sub apply_all_roles_with_method {
 
     if ( scalar @$roles == 1 ) {
         my ( $role, $params ) = @{ $roles->[0] };
-        $role->meta->$apply_method( $meta,
-            ( defined $params ? %$params : () ) );
+        $role->meta->apply( $meta, ( defined $params ? %$params : () ) );
     }
     else {
-        Moose::Meta::Role->combine( @$roles )->$apply_method($meta);
+        Moose::Meta::Role->combine( @$roles )->apply($meta);
     }
 }
 
@@ -229,13 +221,6 @@ actually used internally by both L<Moose> and L<Moose::Role>, and the
 C<@roles> will be pre-processed through L<Data::OptList::mkopt>
 to allow for the additional arguments to be passed. 
 
-=item B<apply_all_roles_with_method ($applicant, $method, @roles)>
-
-This function works just like C<apply_all_roles()>, except it allows
-you to specify what method will be called on the role metaclass when
-applying it to the C<$applicant>. This exists primarily so one can use
-the C<< Moose::Meta::Role->apply_to_metaclass_instance() >> method.
-
 =item B<get_all_attribute_values($meta, $instance)>
 
 Returns the values of the C<$instance>'s fields keyed by the attribute names.
diff --git a/lib/Moose/Util/MetaRole.pm b/lib/Moose/Util/MetaRole.pm
new file mode 100644 (file)
index 0000000..4724da7
--- /dev/null
@@ -0,0 +1,257 @@
+package Moose::Util::MetaRole;
+
+use strict;
+use warnings;
+
+use List::MoreUtils qw( all );
+
+sub apply_metaclass_roles {
+    my %options = @_;
+
+    my $for = $options{for_class};
+
+    my $meta = _make_new_metaclass( $for, \%options );
+
+    for my $tor_class ( grep { $options{ $_ . '_roles' } }
+        qw( constructor_class destructor_class ) ) {
+
+        my $class = _make_new_class(
+            $meta->$tor_class(),
+            $options{ $tor_class . '_roles' }
+        );
+
+        $meta->$tor_class($class);
+    }
+
+    return $meta;
+}
+
+sub _make_new_metaclass {
+    my $for     = shift;
+    my $options = shift;
+
+    return $for->meta()
+        unless grep { exists $options->{ $_ . '_roles' } }
+            qw(
+            metaclass
+            attribute_metaclass
+            method_metaclass
+            instance_metaclass
+    );
+
+    my $new_metaclass
+        = _make_new_class( ref $for->meta(), $options->{metaclass_roles} );
+
+    my $old_meta = $for->meta();
+
+    # This could get called for a Moose::Meta::Role as well as a Moose::Meta::Class
+    my %classes = map {
+        $_ => _make_new_class( $old_meta->$_(), $options->{ $_ . '_roles' } )
+        }
+        grep { $old_meta->can($_) }
+        qw(
+        attribute_metaclass
+        method_metaclass
+        instance_metaclass
+    );
+
+    return $new_metaclass->reinitialize( $for, %classes );
+}
+
+sub apply_base_class_roles {
+    my %options = @_;
+
+    my $for = $options{for_class};
+
+    my $meta = $for->meta();
+
+    my $new_base = _make_new_class(
+        $for,
+        $options{roles},
+        [ $meta->superclasses() ],
+    );
+
+    $meta->superclasses($new_base)
+        if $new_base ne $meta->name();
+}
+
+sub _make_new_class {
+    my $existing_class = shift;
+    my $roles          = shift;
+    my $superclasses   = shift || [$existing_class];
+
+    return $existing_class unless $roles;
+
+    my $meta = $existing_class->meta();
+
+    return $existing_class
+        if $meta->can('does_role') && all { $meta->does_role($_) } @{$roles};
+
+    return Moose::Meta::Class->create_anon_class(
+        superclasses => $superclasses,
+        roles        => $roles,
+        cache        => 1,
+    )->name();
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Moose::Util::MetaRole - Apply roles to any metaclass, as well as the object base class
+
+=head1 SYNOPSIS
+
+  package MyApp::Moose;
+
+  use strict;
+  use warnings;
+
+  use Moose ();
+  use Moose::Exporter;
+  use Moose::Util::Meta::Role;
+
+  use MyApp::Role::Meta::Class;
+  use MyApp::Role::Meta::Method::Constructor;
+  use MyApp::Role::Object;
+
+  Moose::Exporter->setup_import_methods( also => 'Moose' );
+
+  sub init_meta {
+      shift;
+      my %options = @_;
+
+      Moose->init_meta(%options);
+
+      Moose::Util::MetaRole::apply_metaclass_roles(
+          for_class               => $options{for_class},
+          metaclass_roles         => ['MyApp::Role::Meta::Class'],
+          constructor_class_roles => ['MyApp::Role::Meta::Method::Constructor'],
+      );
+
+      Moose::Util::MetaRole::apply_base_class_roles(
+          for_class => $options{for_class},
+          roles     => ['MyApp::Role::Object'],
+      );
+
+      return $options{for_class}->meta();
+  }
+
+=head1 DESCRIPTION
+
+B<The whole concept behind this module is still considered
+experimental, and it could go away in the future!>
+
+This utility module is designed to help authors of Moose extensions
+write extensions that are able to cooperate with other Moose
+extensions. To do this, you must write your extensions as roles, which
+can then be dynamically applyied to the caller's metaclasses.
+
+This module makes sure to preserve any existing superclasses and roles
+already set for the meta objects, which means that any number of
+extensions can apply roles in any order.
+
+=head1 USAGE
+
+B<It is very important that you only call this module's functions when
+your module is imported by the caller>. The process of applying roles
+to the metaclass reinitializes the metaclass object, which wipes out
+any existing attributes already defined. However, as long as you do
+this when your module is imported, the caller should not have any
+attributes defined yet.
+
+The easiest way to ensure that this happens is to use
+L<Moose::Exporter> and provide an C<init_meta> method that will be
+called when imported.
+
+=head1 FUNCTIONS
+
+This module provides two functions.
+
+=head2 apply_metaclass_roles( ... )
+
+This function will apply roles to one or more metaclasses for the
+specified class. It accepts the following parameters:
+
+=over 4
+
+=item * for_class => $name
+
+This specifies the class for which to alter the meta classes.
+
+=item * metaclass_roles => \@roles
+
+=item * attribute_metaclass_roles => \@roles
+
+=item * method_metaclass_roles => \@roles
+
+=item * instance_metaclass_roles => \@roles
+
+=item * constructor_class_roles => \@roles
+
+=item * destructor_class_roles => \@roles
+
+These parameter all specify one or more roles to be applied to the
+specified metaclass. You can pass any or all of these parameters at
+once.
+
+=back
+
+=head2 apply_base_class_roles( for_class => $class, roles => \@roles )
+
+This function will apply the specified roles to the object's base class.
+
+=head1 PROBLEMS WITH METACLASS ROLES AND SUBCLASS
+
+Because of the way this module works, there is an ordering problem
+which occurs in certain situations. This sequence of events causes an
+error:
+
+=over 4
+
+=item 1.
+
+There is a class (ClassA) which uses some extension(s) that apply
+roles to the metaclass.
+
+=item 2.
+
+You have another class (ClassB) which wants to subclass ClassA and
+apply some more extensions.
+
+=back
+
+Normally, the call to C<extends> will happen at run time, I<after> the
+additional extensions are applied. This causes an error when we try to
+make the metaclass for ClassB compatible with the metaclass for
+ClassA.
+
+We hope to be able to fix this in the future.
+
+For now the workaround is for ClassB to make sure it extends ClassA
+I<before> it loads extensions:
+
+  package ClassB;
+
+  use Moose;
+
+  BEGIN { extends 'ClassA' }
+
+  use MooseX::SomeExtension;
+
+=head1 AUTHOR
+
+Dave Rolsky E<lt>autarch@urth.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2008 by Infinity Interactive, Inc.
+
+L<http://www.iinteractive.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/t/050_metaclasses/015_metarole.t b/t/050_metaclasses/015_metarole.t
new file mode 100644 (file)
index 0000000..d2594e9
--- /dev/null
@@ -0,0 +1,382 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 59;
+
+use Moose::Util::MetaRole;
+
+
+{
+    package My::Meta::Class;
+    use Moose;
+    extends 'Moose::Meta::Class';
+}
+
+{
+    package My::Meta::Attribute;
+    use Moose;
+    extends 'Moose::Meta::Attribute';
+}
+
+{
+    package My::Meta::Method;
+    use Moose;
+    extends 'Moose::Meta::Method';
+}
+
+{
+    package My::Meta::Instance;
+    use Moose;
+    extends 'Moose::Meta::Instance';
+}
+
+{
+    package My::Meta::MethodConstructor;
+    use Moose;
+    extends 'Moose::Meta::Method::Constructor';
+}
+
+{
+    package My::Meta::MethodDestructor;
+    use Moose;
+    extends 'Moose::Meta::Method::Destructor';
+}
+
+{
+    package Role::Foo;
+    use Moose::Role;
+    has 'foo' => ( is => 'ro', default => 10 );
+}
+
+{
+    package My::Class;
+
+    use Moose;
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class       => 'My::Class',
+        metaclass_roles => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        'apply Role::Foo to My::Class->meta()' );
+    is( My::Class->meta()->foo(), 10,
+        '... and call foo() on that meta object' );
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class                 => 'My::Class',
+        attribute_metaclass_roles => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class->meta()'s attribute metaclass} );
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        '... My::Class->meta() still does Role::Foo' );
+
+    My::Class->meta()->add_attribute( 'size', is => 'ro' );
+    is( My::Class->meta()->get_attribute('size')->foo(), 10,
+        '... call foo() on an attribute metaclass object' );
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class              => 'My::Class',
+        method_metaclass_roles => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class->meta()'s method metaclass} );
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        '... My::Class->meta() still does Role::Foo' );
+    ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s attribute metaclass still does Role::Foo} );
+
+    My::Class->meta()->add_method( 'bar' => sub { 'bar' } );
+    is( My::Class->meta()->get_method('bar')->foo(), 10,
+        '... call foo() on a method metaclass object' );
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class              => 'My::Class',
+        instance_metaclass_roles => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->instance_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class->meta()'s instance metaclass} );
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        '... My::Class->meta() still does Role::Foo' );
+    ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s attribute metaclass still does Role::Foo} );
+    ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s method metaclass still does Role::Foo} );
+
+    is( My::Class->meta()->get_meta_instance()->foo(), 10,
+        '... call foo() on an instance metaclass object' );
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class               => 'My::Class',
+        constructor_class_roles => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->constructor_class()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class->meta()'s constructor class} );
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        '... My::Class->meta() still does Role::Foo' );
+    ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s attribute metaclass still does Role::Foo} );
+    ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s method metaclass still does Role::Foo} );
+    ok( My::Class->meta()->instance_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s instance metaclass still does Role::Foo} );
+
+    # Actually instantiating the constructor class is too freaking hard!
+    ok( My::Class->meta()->constructor_class()->can('foo'),
+        '... constructor class has a foo method' );
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class              => 'My::Class',
+        destructor_class_roles => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->destructor_class()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class->meta()'s destructor class} );
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        '... My::Class->meta() still does Role::Foo' );
+    ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s attribute metaclass still does Role::Foo} );
+    ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s method metaclass still does Role::Foo} );
+    ok( My::Class->meta()->instance_metaclass()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s instance metaclass still does Role::Foo} );
+    ok( My::Class->meta()->constructor_class()->meta()->does_role('Role::Foo'),
+        q{... My::Class->meta()'s constructor class still does Role::Foo} );
+
+    # same problem as the constructor class
+    ok( My::Class->meta()->destructor_class()->can('foo'),
+        '... destructor class has a foo method' );
+}
+
+{
+    Moose::Util::MetaRole::apply_base_class_roles(
+        for_class => 'My::Class',
+        roles     => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->does_role('Role::Foo'),
+        'apply Role::Foo to My::Class base class' );
+    is( My::Class->new()->foo(), 10,
+        '... call foo() on a My::Class object' );
+}
+
+{
+    package My::Class2;
+
+    use Moose;
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class                 => 'My::Class2',
+        metaclass_roles           => ['Role::Foo'],
+        attribute_metaclass_roles => ['Role::Foo'],
+        method_metaclass_roles    => ['Role::Foo'],
+        instance_metaclass_roles  => ['Role::Foo'],
+        constructor_class_roles   => ['Role::Foo'],
+        destructor_class_roles    => ['Role::Foo'],
+    );
+
+    ok( My::Class2->meta()->meta()->does_role('Role::Foo'),
+        'apply Role::Foo to My::Class2->meta()' );
+    is( My::Class2->meta()->foo(), 10,
+        '... and call foo() on that meta object' );
+    ok( My::Class2->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s attribute metaclass} );
+    My::Class2->meta()->add_attribute( 'size', is => 'ro' );
+
+    is( My::Class2->meta()->get_attribute('size')->foo(), 10,
+        '... call foo() on an attribute metaclass object' );
+
+    ok( My::Class2->meta()->method_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s method metaclass} );
+
+    My::Class2->meta()->add_method( 'bar' => sub { 'bar' } );
+    is( My::Class2->meta()->get_method('bar')->foo(), 10,
+        '... call foo() on a method metaclass object' );
+
+    ok( My::Class2->meta()->instance_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s instance metaclass} );
+    is( My::Class2->meta()->get_meta_instance()->foo(), 10,
+        '... call foo() on an instance metaclass object' );
+
+    ok( My::Class2->meta()->constructor_class()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s constructor class} );
+    ok( My::Class2->meta()->constructor_class()->can('foo'),
+        '... constructor class has a foo method' );
+
+    ok( My::Class2->meta()->destructor_class()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s destructor class} );
+    ok( My::Class2->meta()->destructor_class()->can('foo'),
+        '... destructor class has a foo method' );
+}
+
+
+{
+    package My::Meta;
+
+    use Moose::Exporter;
+    Moose::Exporter->setup_import_methods( also => 'Moose' );
+
+    sub init_meta {
+        shift;
+        my %p = @_;
+
+        Moose->init_meta( %p, metaclass => 'My::Meta::Class' );
+    }
+}
+
+{
+    package My::Class3;
+
+    My::Meta->import();
+}
+
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class                 => 'My::Class3',
+        metaclass_roles           => ['Role::Foo'],
+    );
+
+    ok( My::Class3->meta()->meta()->does_role('Role::Foo'),
+        'apply Role::Foo to My::Class3->meta()' );
+    is( My::Class3->meta()->foo(), 10,
+        '... and call foo() on that meta object' );
+    ok( ( grep { $_ eq 'My::Meta::Class' } My::Class3->meta()->meta()->superclasses() ),
+        'apply_metaclass_roles() does not interfere with metaclass set via Moose->init_meta()' );
+}
+
+{
+    package Role::Bar;
+    use Moose::Role;
+    has 'bar' => ( is => 'ro', default => 200 );
+}
+
+{
+    package My::Class4;
+    use Moose;
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class                 => 'My::Class4',
+        metaclass_roles           => ['Role::Foo'],
+    );
+
+    ok( My::Class4->meta()->meta()->does_role('Role::Foo'),
+        'apply Role::Foo to My::Class4->meta()' );
+
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class                 => 'My::Class4',
+        metaclass_roles           => ['Role::Bar'],
+    );
+
+    ok( My::Class4->meta()->meta()->does_role('Role::Bar'),
+        'apply Role::Bar to My::Class4->meta()' );
+    ok( My::Class4->meta()->meta()->does_role('Role::Foo'),
+        '... and My::Class4->meta() still does Role::Foo' );
+}
+
+{
+    package My::Class5;
+    use Moose;
+
+    extends 'My::Class';
+}
+
+{
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        q{My::Class5->meta()'s does Role::Foo because it extends My::Class} );
+    ok( My::Class->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{My::Class5->meta()'s attribute metaclass also does Role::Foo} );
+    ok( My::Class->meta()->method_metaclass()->meta()->does_role('Role::Foo'),
+        q{My::Class5->meta()'s method metaclass also does Role::Foo} );
+    ok( My::Class->meta()->instance_metaclass()->meta()->does_role('Role::Foo'),
+        q{My::Class5->meta()'s instance metaclass also does Role::Foo} );
+    ok( My::Class->meta()->constructor_class()->meta()->does_role('Role::Foo'),
+        q{My::Class5->meta()'s constructor class also does Role::Foo} );
+    ok( My::Class->meta()->destructor_class()->meta()->does_role('Role::Foo'),
+        q{My::Class->meta()'s destructor class also does Role::Foo} );
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class       => 'My::Class5',
+        metaclass_roles => ['Role::Bar'],
+    );
+
+    ok( My::Class5->meta()->meta()->does_role('Role::Bar'),
+        q{apply Role::Bar My::Class5->meta()} );
+    ok( My::Class5->meta()->meta()->does_role('Role::Foo'),
+        q{... and My::Class5->meta() still does Role::Foo} );
+}
+
+SKIP:
+{
+    skip
+        'These tests will fail until Moose::Meta::Class->_fix_metaclass_incompatibility is much smarter.',
+        2;
+{
+    package My::Class6;
+    use Moose;
+
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class       => 'My::Class6',
+        metaclass_roles => ['Role::Bar'],
+    );
+
+    extends 'My::Class';
+}
+
+{
+    ok( My::Class6->meta()->meta()->does_role('Role::Bar'),
+        q{apply Role::Bar My::Class6->meta() before extends} );
+    ok( My::Class6->meta()->meta()->does_role('Role::Foo'),
+        q{... and My::Class6->meta() does Role::Foo because it extends My::Class} );
+}
+}
+
+# This is the hack needed to work around the
+# _fix_metaclass_incompatibility problem. You must call extends()
+# (which in turn calls _fix_metaclass_imcompatibility) _before_ you
+# apply more extensions in the subclass.
+{
+    package My::Class7;
+    use Moose;
+
+    # In real usage this would go in a BEGIN block so it happened
+    # before apply_metaclass_roles was called by an extension.
+    extends 'My::Class';
+
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class       => 'My::Class7',
+        metaclass_roles => ['Role::Bar'],
+    );
+}
+
+{
+    ok( My::Class7->meta()->meta()->does_role('Role::Bar'),
+        q{apply Role::Bar My::Class7->meta() before extends} );
+    ok( My::Class7->meta()->meta()->does_role('Role::Foo'),
+        q{... and My::Class7->meta() does Role::Foo because it extends My::Class} );
+}
diff --git a/t/050_metaclasses/016_metarole_w_metaclass_pm.t b/t/050_metaclasses/016_metarole_w_metaclass_pm.t
new file mode 100644 (file)
index 0000000..4db435e
--- /dev/null
@@ -0,0 +1,109 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 8;
+
+use Moose::Util::MetaRole;
+
+BEGIN
+{
+    package My::Meta::Class;
+    use Moose;
+    extends 'Moose::Meta::Class';
+}
+
+BEGIN
+{
+    package My::Meta::Attribute;
+    use Moose;
+    extends 'Moose::Meta::Attribute';
+}
+
+BEGIN
+{
+    package My::Meta::Method;
+    use Moose;
+    extends 'Moose::Meta::Method';
+}
+
+BEGIN
+{
+    package My::Meta::Instance;
+    use Moose;
+    extends 'Moose::Meta::Instance';
+}
+
+BEGIN
+{
+    package Role::Foo;
+    use Moose::Role;
+    has 'foo' => ( is => 'ro', default => 10 );
+}
+
+{
+    package My::Class;
+
+    use metaclass 'My::Meta::Class';
+    use Moose;
+}
+
+{
+    package My::Class2;
+
+    use metaclass 'My::Meta::Class' => (
+        attribute_metaclass => 'My::Meta::Attribute',
+        method_metaclass    => 'My::Meta::Method',
+        instance_metaclass  => 'My::Meta::Instance',
+    );
+
+    use Moose;
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class       => 'My::Class',
+        metaclass_roles => ['Role::Foo'],
+    );
+
+    ok( My::Class->meta()->meta()->does_role('Role::Foo'),
+        'apply Role::Foo to My::Class->meta()' );
+    has_superclass( My::Class->meta(), 'My::Meta::Class',
+                    'apply_metaclass_roles works with metaclass.pm' );
+}
+
+{
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class                 => 'My::Class2',
+        attribute_metaclass_roles => ['Role::Foo'],
+        method_metaclass_roles    => ['Role::Foo'],
+        instance_metaclass_roles  => ['Role::Foo'],
+    );
+
+    ok( My::Class2->meta()->attribute_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s attribute metaclass} );
+    has_superclass( My::Class2->meta()->attribute_metaclass(), 'My::Meta::Attribute',
+                    '... and this does not interfere with attribute metaclass set via metaclass.pm' );
+    ok( My::Class2->meta()->method_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s method metaclass} );
+    has_superclass( My::Class2->meta()->method_metaclass(), 'My::Meta::Method',
+                    '... and this does not interfere with method metaclass set via metaclass.pm' );
+    ok( My::Class2->meta()->instance_metaclass()->meta()->does_role('Role::Foo'),
+        q{apply Role::Foo to My::Class2->meta()'s instance metaclass} );
+    has_superclass( My::Class2->meta()->instance_metaclass(), 'My::Meta::Instance',
+                    '... and this does not interfere with instance metaclass set via metaclass.pm' );
+}
+
+# like isa_ok but works with a class name, not just refs
+sub has_superclass {
+    my $thing  = shift;
+    my $parent = shift;
+    my $desc   = shift;
+
+    my %supers = map { $_ => 1 } $thing->meta()->superclasses();
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    ok( $supers{$parent}, $desc );
+}