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;
--- /dev/null
+#!/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');
+
--- /dev/null
+#!/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');
+
+
+
+