On Sun, May 09, 2010 at 04:56:21AM -0400, Mike Friedman wrote:
> Greetings,
> 
> I've got a class X. X has a metaclass M. I want M to make X do role Y.
> That's it. I have been banging my head against a half dozen Moose
> documentation pages but can't seem to puzzle out this seemingly simple
> operation.

So... is there a reason that using 'with' in the class definition isn't
sufficient? (The subject line mentions runtime application, but your
code sample below is a Moose::Exporter package, which seems to be
counter to that idea.)

> Which one of these things do I need to use? Moose::Exporter,
> Moose::Util::MetaRole? (This seems to be about meta roles rather than
> roles, but I don't _think_ I need to use a meta role to just make a
> class consume a given role, or do I?)

Yes, MetaRole is for metaclass roles, as the name implies.
Moose::Exporter is for if you want to make custom exporters (this can do
what you want, see below, but there's really no need for it unless
you're also doing other things like exporting functions or applying
metaclass roles).

> I also found this thing: Moose::Meta::Role::Application::ToClass which
> sounds really nice but has zero documentation. I looked at the source
> but, of course, I have no idea WTF is going on.

This is an entirely internal class that you really should have no reason
to use directly.

> Here is one thing I tried, which doesn't seem to complain, but also
> doesn't seem to apply the role. (In my test, the class does not cause
> an exception even though it fails to implement a method required by
> the role.)

You were close - the reason this doesn't work is because initialize is
for creating a metaclass for a class that doesn't yet have one; it's
typically not very useful for roles. The thing you're looking for is
Moose::Util::apply_all_roles, like so:

  package MyApp;

  use Moose ();
  use Moose::Exporter;

  use MyApp::Meta::Class;

  Moose::Exporter->setup_import_methods(
      with_meta  => [ 'handler' ],
      also       => 'Moose',
  );

  sub init_meta {
      my $class = shift;
      my %options = @_;

      my $meta = Moose->init_meta( @_ , metaclass => 'MyApp::Meta::Class' );
      Moose::Util::apply_all_roles( $options{for_class}, 'MyApp::Role::App' );

      return $meta;
  }

  ...

  1;

On another note, it's typically preferable to create metaclass traits
rather than writing an actual metaclass yourself - this makes it work
more nicely with other extensions (such as various MooseX modules). It
probably won't harm anything if this is an app-specific metaclass
extension that you're only writing one variation of, but it also
wouldn't harm anything for it to be a role, and getting used to the good
practice would probably help in the future. In that case, the exporter
would look something like:

  package MyApp;

  use Moose ();
  use Moose::Exporter;

  my ($import, $unimport, $init_meta) = Moose::Exporter->setup_import_methods(
      with_meta  => [ 'handler' ],
      also       => 'Moose',
      install    => [ 'import', 'unimport' ],
      class_metaroles => {
          class => [ 'MyApp::Meta::Trait::Class' ],
      },
  );

  sub init_meta {
      my ($package, %options) = @_;
      Moose->init_meta(%options);
      Moose::Util::apply_all_roles($options{for_class}, 'MyApp::Role::App');
      goto $init_meta;
  }

  ...

  1;

-doy

Reply via email to