Skip Menu |

This queue is for tickets about the Moose CPAN distribution.

Report information
The Basics
Id: 51561
Status: resolved
Priority: 0/
Queue: Moose

People
Owner: Nobody in particular
Requestors: jasonk [...] cpan.org
Cc: ROBINS [...] cpan.org
AdminCc:

Bug Information
Severity: Important
Broken in: 0.92
Fixed in: (no value)



Subject: Moose::Exporter generated init_meta won't init a new package
The subject line is a lousy description, but it turned out to be fairly difficult to explain the problem, even after I spent the last couple of hours tracking it down. Basically what it means is this. Assuming you have a role and a Moose::Exporter created package like this: package MyApp::Role; use Moose::Role; sub foo { print "FOO!\n" } package MyApp::Moose; use Moose::Exporter; Moose::Exporter->setup_export_methods( base_class_roles => ['MyApp::Role'], also => ['Moose'], ); And you then use it to create a new package, like so: package MyApp::Foo; use MyApp::Moose; And then you call the method that the role provided, what do you think will happen? MyApp::Foo->foo; Did you expect it to print "FOO!"? If you did, you'll be sorry, what you get instead is: Can't locate object method "foo" via package "MyApp::Foo" at ... Strange? What's happening is that when the Moose::Exporter provided import method is called, it includes this chunk of code: for my $c ( grep { $_->can('init_meta') } $class, @{$exports_from} ) { # snip... $c->init_meta( for_class => $CALLER, metaclass => $metaclass ); In our example this results in the following calls: MyApp::Moose->init_meta( for_class => 'MyApp::Foo', metaclass => undef ); Moose->init_meta( for_class => 'MyApp::Foo', metaclass => undef, ); The problem is that the generated init_meta method begins with: return unless Class::MOP::class_of( $options{ 'for_class' } ); So when MyApp::Moose->init_meta gets called, Class::MOP::class_of( 'MyApp::Foo' ) returns undef and the calls to Moose::Util::MetaRole get skipped. Then Moose->init_meta gets called, and MyApp::Foo gets initialized correctly, but by then it's too late, it already missed it's chance to get the roles applied. I've attached a git patch to lib/Moose/Exporter.pm with a fix that seems to work for me without breaking anything else in the test suite, as well as a patch to t/050_metaclasses/023_easy_init_meta.t that adds a test that demonstrates the problem. -- www.jasonkohles.com
Subject: 0001-Fixed-init_meta-bug.patch
From 5faf7fb624bf2196e64f82878e9515b9abdd5a76 Mon Sep 17 00:00:00 2001 From: Jason Kohles <email@jasonkohles.com> Date: Sun, 15 Nov 2009 19:10:44 -0800 Subject: [PATCH] Fixed init_meta bug --- lib/Moose/Exporter.pm | 3 ++- t/050_metaclasses/023_easy_init_meta.t | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/Moose/Exporter.pm b/lib/Moose/Exporter.pm index 0e07062..095db96 100644 --- a/lib/Moose/Exporter.pm +++ b/lib/Moose/Exporter.pm @@ -513,7 +513,8 @@ sub _make_init_meta { shift; my %options = @_; - return unless Class::MOP::class_of( $options{for_class} ); + Moose->init_meta( %options ) + unless Class::MOP::class_of( $options{for_class} ); Moose::Util::MetaRole::apply_metaclass_roles( for_class => $options{for_class}, diff --git a/t/050_metaclasses/023_easy_init_meta.t b/t/050_metaclasses/023_easy_init_meta.t index 200d734..1fb90b1 100644 --- a/t/050_metaclasses/023_easy_init_meta.t +++ b/t/050_metaclasses/023_easy_init_meta.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 13; +use Test::More tests => 17; use Test::Moose qw(does_ok); { @@ -45,6 +45,16 @@ use Test::Moose qw(does_ok); } { + package Foo3; + Foo::Exporter->import; + + ::isa_ok('Foo3', 'Moose::Object'); + ::isa_ok(Foo3->meta, 'Moose::Meta::Class'); + ::does_ok(Foo3->meta, 'Foo::Trait::Class'); + ::does_ok('Foo3', 'Foo::Role::Base'); +} + +{ package Foo::Exporter::WithMoose; use Moose (); use Moose::Exporter; -- 1.6.2.5
Hmmm. This is a little tricky. The reason it works like this currently is because previously (before stuff like MetaRole existed), the way you had to write extensions was by writing your own init_meta, that looked something like this: sub init_meta { shift; return Moose->init_meta(@_, metaclass => 'My::MetaClass'); } But, Moose::init_meta won't reinitialize metaclasses that already exist (since that would break lots of things), so blindly calling Moose->init_meta if a metaclass doesn't exist yet isn't quite right either. (This is also why we can't just change the order of init_meta calls to do the ones specified by 'also' first... that would mean that a custom init_meta that exists in your exporter wouldn't get a chance to override options when calling Moose->init_meta.) I'm actually not sure what the right solution here is... it's possible we could just drop support for this old style of extensions, but I don't know how much this would break. Any thoughts from anyone else?
On Sun Nov 15 22:50:26 2009, DOY wrote: Show quoted text
> Hmmm. This is a little tricky. The reason it works like this currently > is because previously (before stuff like MetaRole existed), the way you > had to write extensions was by writing your own init_meta, that looked > something like this: > > sub init_meta { > shift; > return Moose->init_meta(@_, metaclass => 'My::MetaClass'); > } > > But, Moose::init_meta won't reinitialize metaclasses that already exist > (since that would break lots of things), so blindly calling > Moose->init_meta if a metaclass doesn't exist yet isn't quite right > either. (This is also why we can't just change the order of init_meta > calls to do the ones specified by 'also' first... that would mean that a > custom init_meta that exists in your exporter wouldn't get a chance to > override options when calling Moose->init_meta.) I'm actually not sure > what the right solution here is... it's possible we could just drop > support for this old style of extensions, but I don't know how much this > would break. Any thoughts from anyone else?
I would also like to drop support for old extensions. I think I wrote a number of those extensions myself, and obviously I've updated mine to the new style. I'd be surprised if there was anything on CPAN that still used this style. We probably need a proper deprecation cycle first, though, since this has never warned or been mentioned as deprecated in Changes.
This was fixed in 2.0600.