Converted this extension to use MetaRole
Dave Rolsky [Fri, 11 Sep 2009 14:20:32 +0000 (09:20 -0500)]
This is a bit gross, but it works, and it cooperates with MX::StrictConstructor, at least.

lib/MooseX/Singleton.pm
lib/MooseX/Singleton/Role/Meta/Class.pm [moved from lib/MooseX/Singleton/Meta/Class.pm with 57% similarity]
lib/MooseX/Singleton/Role/Meta/Instance.pm [moved from lib/MooseX/Singleton/Meta/Instance.pm with 74% similarity]
lib/MooseX/Singleton/Role/Meta/Method/Constructor.pm [moved from lib/MooseX/Singleton/Meta/Method/Constructor.pm with 67% similarity]
lib/MooseX/Singleton/Role/Object.pm [moved from lib/MooseX/Singleton/Object.pm with 80% similarity]
t/006-cooperative.t [new file with mode: 0644]

index bde5065..3401c7e 100644 (file)
@@ -1,6 +1,6 @@
 package MooseX::Singleton;
 
-use Moose 0.82 ();
+use Moose 0.89_02 ();
 use Moose::Exporter;
 use MooseX::Singleton::Object;
 use MooseX::Singleton::Meta::Class;
@@ -12,13 +12,31 @@ Moose::Exporter->setup_import_methods( also => 'Moose' );
 
 sub init_meta {
     shift;
-    Moose->init_meta(
-        @_,
-        base_class => 'MooseX::Singleton::Object',
-        metaclass  => 'MooseX::Singleton::Meta::Class',
+    my %p = @_;
+
+    Moose->init_meta(%p);
+
+    my $caller = $p{for_class};
+
+    Moose::Util::MetaRole::apply_metaclass_roles(
+        for_class       => $caller,
+        metaclass_roles => ['MooseX::Singleton::Role::Meta::Class'],
+        instance_metaclass_roles =>
+            ['MooseX::Singleton::Role::Meta::Instance'],
+        constructor_class_roles =>
+            ['MooseX::Singleton::Role::Meta::Method::Constructor'],
     );
+
+    Moose::Util::MetaRole::apply_base_class_roles(
+        for_class => $caller,
+        roles =>
+            ['MooseX::Singleton::Role::Object'],
+    );
+
+    return $caller->meta();
 }
 
+
 1;
 
 __END__
similarity index 57%
rename from lib/MooseX/Singleton/Meta/Class.pm
rename to lib/MooseX/Singleton/Role/Meta/Class.pm
index 609c250..a8a43ee 100644 (file)
@@ -1,25 +1,9 @@
 #!/usr/bin/env perl
-package MooseX::Singleton::Meta::Class;
-use Moose;
+package MooseX::Singleton::Role::Meta::Class;
+use Moose::Role;
 use MooseX::Singleton::Meta::Instance;
 use MooseX::Singleton::Meta::Method::Constructor;
 
-extends 'Moose::Meta::Class';
-
-sub initialize {
-    my $class = shift;
-    my $pkg   = shift;
-
-    my $self = $class->SUPER::initialize(
-        $pkg,
-        instance_metaclass => 'MooseX::Singleton::Meta::Instance',
-        constructor_class  => 'MooseX::Singleton::Meta::Method::Constructor',
-        @_,
-    );
-
-    return $self;
-}
-
 sub existing_singleton {
     my ($class) = @_;
     my $pkg = $class->name;
@@ -63,13 +47,13 @@ __END__
 
 =head1 NAME
 
-MooseX::Singleton::Meta::Class
+MooseX::Singleton::Role::Meta::Class - Metaclass role for MooseX::Singleton
 
 =head1 DESCRIPTION
 
-This metaclass is where the forcing of one instance occurs. The first call to
-C<construct_instance> is run normally (and then cached). Subsequent calls will
-return the cached version.
+This metaclass role makes sure that there is only ever one instance of an
+object for a singleton class. The first call to C<construct_instance> is run
+normally (and then cached). Subsequent calls will return the cached version.
 
 =cut
 
similarity index 74%
rename from lib/MooseX/Singleton/Meta/Instance.pm
rename to lib/MooseX/Singleton/Role/Meta/Instance.pm
index a56db58..b13b0ca 100644 (file)
@@ -1,10 +1,8 @@
 #!/usr/bin/env perl
-package MooseX::Singleton::Meta::Instance;
-use Moose;
+package MooseX::Singleton::Role::Meta::Instance;
+use Moose::Role;
 use Scalar::Util 'weaken';
 
-extends 'Moose::Meta::Instance';
-
 sub get_singleton_instance {
     my ($self, $instance) = @_;
 
@@ -21,40 +19,40 @@ sub get_singleton_instance {
     return $instance->meta->name->new;
 }
 
-sub clone_instance {
+override clone_instance => sub  {
     my ($self, $instance) = @_;
     $self->get_singleton_instance($instance);
-}
+};
 
-sub get_slot_value {
+override get_slot_value => sub  {
     my ($self, $instance, $slot_name) = @_;
     $self->is_slot_initialized($instance, $slot_name) ? $self->get_singleton_instance($instance)->{$slot_name} : undef;
-}
+};
 
-sub set_slot_value {
+override set_slot_value => sub  {
     my ($self, $instance, $slot_name, $value) = @_;
     $self->get_singleton_instance($instance)->{$slot_name} = $value;
-}
+};
 
-sub deinitialize_slot {
+override deinitialize_slot => sub  {
     my ( $self, $instance, $slot_name ) = @_;
     delete $self->get_singleton_instance($instance)->{$slot_name};
-}
+};
 
-sub is_slot_initialized {
+override is_slot_initialized => sub  {
     my ($self, $instance, $slot_name, $value) = @_;
     exists $self->get_singleton_instance($instance)->{$slot_name} ? 1 : 0;
-}
+};
 
-sub weaken_slot_value {
+override weaken_slot_value => sub  {
     my ($self, $instance, $slot_name) = @_;
     weaken $self->get_singleton_instance($instance)->{$slot_name};
-}
+};
 
-sub inline_slot_access {
+override inline_slot_access => sub  {
     my ($self, $instance, $slot_name) = @_;
     sprintf "%s->meta->instance_metaclass->get_singleton_instance(%s)->{%s}", $instance, $instance, $slot_name;
-}
+};
 
 no Moose;
 
@@ -66,13 +64,12 @@ __END__
 
 =head1 NAME
 
-MooseX::Singleton::Meta::Instance
+MooseX::Singleton::Role::Meta::Instance - Instance metaclass role for MooseX::Singleton
 
 =head1 DESCRIPTION
 
-This instance metaclass manages attribute access and storage. When accessing an
-attribute, it will convert a bare package to its cached singleton instance
-(creating it if necessary).
+This role overrides all object access so that it gets the appropriate
+singleton instance for the class.
 
 =cut
 
@@ -1,10 +1,8 @@
 #!/usr/bin/env perl
-package MooseX::Singleton::Meta::Method::Constructor;
-use Moose;
+package MooseX::Singleton::Role::Meta::Method::Constructor;
+use Moose::Role;
 
-extends 'Moose::Meta::Method::Constructor';
-
-sub _initialize_body {
+override _initialize_body => sub {
     my $self = shift;
     # TODO:
     # the %options should also include a both
@@ -15,7 +13,7 @@ sub _initialize_body {
     # the author, after all, nothing is free)
     my $source = 'sub {';
     $source .= "\n" . 'my $class = shift;';
+
     $source .= "\n" . 'my $existing = do { no strict "refs"; no warnings "once"; \${"$class\::singleton"}; };';
     $source .= "\n" . 'return ${$existing} if ${$existing};';
 
@@ -57,17 +55,30 @@ sub _initialize_body {
         if $e;
 
     $self->{'body'} = $code;
-}
-
-# For CMOP 0.82_01+
-sub _expected_method_class {
-    return 'MooseX::Singleton::Object';
-}
+};
+
+# Ideally we'd be setting this in the constructor, but the new() methods in
+# what the parent classes are not well-factored.
+#
+# This is all a nasty hack, though. We need to fix Class::MOP::Inlined to
+# allow constructor class roles to say "if the parent class has role X,
+# inline".
+override _expected_method_class => sub {
+    my $self = shift;
 
-# For older versions of Moose/CMOP
-sub _expected_constructor_class {
-    return 'MooseX::Singleton::Object';
-}
+    my $super_value = super();
+    if ( $super_value eq 'Moose::Object' ) {
+        for my $parent ( map { Class::MOP::class_of($_) }
+            $self->associated_metaclass->superclasses ) {
+            return $parent->name
+                if $parent->is_anon_class
+                    && grep { $_->name eq 'Moose::Object' }
+                    map { Class::MOP::class_of($_) } $parent->superclasses;
+        }
+    }
+
+    return $super_value;
+};
 
 no Moose;
 
similarity index 80%
rename from lib/MooseX/Singleton/Object.pm
rename to lib/MooseX/Singleton/Role/Object.pm
index 312e472..b09613f 100644 (file)
@@ -1,8 +1,6 @@
 #!/usr/bin/env perl
-package MooseX::Singleton::Object;
-use Moose;
-
-extends 'Moose::Object';
+package MooseX::Singleton::Role::Object;
+use Moose::Role;
 
 sub instance { shift->new }
 
@@ -15,7 +13,7 @@ sub initialize {
   return $class->SUPER::new(@args);
 }
 
-sub new {
+override new => sub {
   my ($class, @args) = @_;
 
   my $existing = $class->meta->existing_singleton;
@@ -25,8 +23,8 @@ sub new {
   # -- rjbs, 2008-02-03
   return $existing if $existing and ! @args;
 
-  return $class->SUPER::new(@args);
-}
+  return super();
+};
 
 sub _clear_instance {
   my ($class) = @_;
@@ -43,7 +41,7 @@ __END__
 
 =head1 NAME
 
-MooseX::Singleton::Object - base class for MooseX::Singleton
+MooseX::Singleton::Object - Object class role for MooseX::Singleton
 
 =head1 DESCRIPTION
 
diff --git a/t/006-cooperative.t b/t/006-cooperative.t
new file mode 100644 (file)
index 0000000..7924cc3
--- /dev/null
@@ -0,0 +1,28 @@
+use strict;
+use warnings;
+
+use Test::More;
+
+BEGIN {
+    eval "require MooseX::StrictConstructor; use Test::Exception; 1;";
+    plan skip_all => 'This test requires MooseX::StrictConstructor and Test::Exception'
+        if $@;
+}
+
+plan 'no_plan';
+
+{
+    package MySingleton;
+    use Moose;
+    use MooseX::Singleton;
+    use MooseX::StrictConstructor;
+
+    has 'attrib' =>
+        is      => 'rw';
+}
+
+throws_ok {
+    MySingleton->new( bad_name => 42 )
+}
+qr/Found unknown attribute/,
+'singleton class also has a strict constructor';