uploadin
Stevan Little [Mon, 27 Feb 2006 14:42:56 +0000 (14:42 +0000)]
lib/Class/MOP/SafeMixin.pm
t/300_basic_safe_mixin.t [new file with mode: 0644]
t/301_safe_mixin_decorators.t [new file with mode: 0644]

index ff9dc9c..d2a7112 100644 (file)
@@ -4,11 +4,60 @@ package Class::MOP::SafeMixin;
 use strict;
 use warnings;
 
+use Scalar::Util 'blessed';
+use Carp         'confess';
+
 our $VERSION = '0.01';
 
-sub meta { 
-    require Class::MOP::Class;
-    Class::MOP::Class->initialize(blessed($_[0]) || $_[0]);
+use base 'Class::MOP::Class';
+
+sub mixin {
+    # fetch the metaclass for the 
+    # caller and the mixin arg
+    my $metaclass = shift;
+    my $mixin     = (shift)->meta;
+    
+    # according to Scala, the 
+    # the superclass of our class
+    # must be a subclass of the 
+    # superclass of the mixin (see above)
+    my ($super_meta)  = $metaclass->superclasses();
+    my ($super_mixin) = $mixin->superclasses();  
+    ($super_meta->isa($super_mixin))
+        || confess "The superclass must extend a subclass of the superclass of the mixin"
+                       if defined $super_mixin && defined $super_meta;
+    
+    # collect all the attributes
+    # and clone them so they can 
+    # associate with the new class
+    my @attributes = map { 
+        $mixin->get_attribute($_)->clone() 
+    } $mixin->get_attribute_list;                     
+    
+    my %methods = map  { 
+        my $method = $mixin->get_method($_);
+        # we want to ignore accessors since
+        # they will be created with the attrs
+        (blessed($method) && $method->isa('Class::MOP::Attribute::Accessor'))
+            ? () : ($_ => $method)
+    } $mixin->get_method_list;    
+
+    # NOTE:
+    # I assume that locally defined methods 
+    # and attributes get precedence over those
+    # from the mixin.
+
+    # add all the attributes in ....
+    foreach my $attr (@attributes) {
+        $metaclass->add_attribute($attr) 
+            unless $metaclass->has_attribute($attr->name);
+    }
+
+    # add all the methods in ....    
+    foreach my $method_name (keys %methods) {
+        $metaclass->alias_method($method_name => $methods{$method_name}) 
+            unless $metaclass->has_method($method_name);
+    }    
 }
 
 1;
diff --git a/t/300_basic_safe_mixin.t b/t/300_basic_safe_mixin.t
new file mode 100644 (file)
index 0000000..0694821
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More no_plan => 1;
+
+BEGIN {
+    use_ok('Class::MOP');
+    use_ok('Class::MOP::SafeMixin');
+}
+
+## Mixin a class without a superclass.
+{
+    package FooMixin;   
+       use metaclass;
+    sub foo { 'FooMixin::foo' }    
+
+    package Foo;
+    use metaclass 'Class::MOP::SafeMixin';
+    Foo->meta->mixin('FooMixin');
+    sub new { (shift)->meta->new_object(@_) }
+}
+
+my $foo = Foo->new();
+isa_ok($foo, 'Foo');
+
+can_ok($foo, 'foo');
+is($foo->foo, 'FooMixin::foo', '... got the right value from the mixin method');
+
+## Mixin a class who shares a common ancestor
+{   
+    package Baz;
+    our @ISA = ('Foo');    
+    sub baz { 'Baz::baz' }     
+
+    package Bar;
+    our @ISA = ('Foo');
+
+       package Foo::Bar;
+    our @ISA = ('Foo', 'Bar'); 
+
+    package Foo::Bar::Baz;
+    our @ISA = ('Foo::Bar');    
+       eval { Foo::Bar::Baz->meta->mixin('Baz') };
+       ::ok(!$@, '... the classes superclass must extend a subclass of the superclass of the mixins');
+}
+
+my $foo_bar_baz = Foo::Bar::Baz->new();
+isa_ok($foo_bar_baz, 'Foo::Bar::Baz');
+isa_ok($foo_bar_baz, 'Foo::Bar');
+isa_ok($foo_bar_baz, 'Foo');
+isa_ok($foo_bar_baz, 'Bar');
+
+can_ok($foo_bar_baz, 'baz');
+is($foo_bar_baz->baz(), 'Baz::baz', '... got the right value from the mixin method');
+
diff --git a/t/301_safe_mixin_decorators.t b/t/301_safe_mixin_decorators.t
new file mode 100644 (file)
index 0000000..777c318
--- /dev/null
@@ -0,0 +1,58 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More no_plan => 1;
+
+BEGIN {
+    use_ok('Class::MOP');
+    use_ok('Class::MOP::SafeMixin');
+}
+
+{
+    package FooMixin;   
+       use metaclass;
+       
+       my %cache;
+       sub MODIFY_CODE_ATTRIBUTES {
+               my ($class, $code, @attrs) = @_;
+               ::diag join ", " => $code, "Attrs: ", @attrs;
+               $cache{$code} = $attrs[0];
+               return ();      
+       }       
+       
+       sub FETCH_CODE_ATTRIBUTES { $cache{$_[1]} }
+       
+    sub foo : before { 'FooMixin::foo::before -> ' }    
+    sub bar : after  { ' -> FooMixin::bar::after'  }    
+    sub baz : around { 
+               my $method = shift;
+               my ($self, @args) = @_;
+               'FooMixin::baz::around(' . $self->$method(@args) . ')'; 
+       }            
+
+    package Foo;
+    use metaclass 'Class::MOP::SafeMixin';
+
+    Foo->meta->mixin('FooMixin');
+    
+    sub new { (shift)->meta->new_object(@_) }
+    
+    sub foo { 'Foo::foo' }
+    sub bar { 'Foo::bar' }
+    sub baz { 'Foo::baz' }        
+}
+
+diag attributes::get(\&FooMixin::foo) . "\n";
+
+my $foo = Foo->new();
+isa_ok($foo, 'Foo');
+
+is($foo->foo(), 'FooMixin::foo::before -> Foo::foo', '... before method worked');
+is($foo->bar(), 'Foo::bar -> FooMixin::bar::after', '... after method worked');
+is($foo->baz(), 'FooMixin::baz::around(Foo::baz)', '... around method worked');
+
+
+
+