use Class::MOP;
use Carp 'confess';
-use Scalar::Util 'weaken', 'blessed';
+use Scalar::Util 'weaken', 'blessed', 'reftype';
our $VERSION = '0.05';
return $self->SUPER::has_method($method_name);
}
+sub add_attribute {
+ my ($self, $name, %params) = @_;
+
+ my @delegations;
+ if ( my $delegation = delete $params{handles} ) {
+ my @method_names_or_hashes = $self->compute_delegation( $name, $delegation, \%params );
+ @delegations = $self->get_delegatable_methods( @method_names_or_hashes );
+ }
+
+ my $ret = $self->SUPER::add_attribute( $name, %params );
+
+ if ( @delegations ) {
+ my $attr = $self->get_attribute( $name );
+ $self->generate_delgate_method( $attr, $_ ) for @delegations;
+ }
+
+ return $ret;
+}
+
+sub generate_delgate_method {
+ my ( $self, $attr, $method ) = @_;
+
+ # FIXME like generated accessors these methods must be regenerated
+ # FIXME the reader may not work for subclasses with weird instances
+
+ my $reader = $attr->generate_reader_method( $attr->name ); # FIXME no need for attr name
+
+ my $method_name = $method->{name};
+ my $new_name = $method->{new_name} || $method_name;
+
+ $self->add_method( $new_name, sub {
+ if ( Scalar::Util::blessed( my $delegate = shift->$reader ) ) {
+ return $delegate->$method_name( @_ );
+ }
+ return;
+ });
+}
+
+sub compute_delegation {
+ my ( $self, $attr_name, $delegation, $params ) = @_;
+
+
+ # either it's a concrete list of method names
+ return $delegation unless ref $delegation; # single method name
+ return @$delegation if reftype($delegation) eq "ARRAY";
+
+ # or it's a generative api
+ my $delegator_meta = $self->_guess_attr_class_or_role( $attr_name, $params );
+ $self->generate_delegation_list( $delegation, $delegator_meta );
+}
+
+sub get_delegatable_methods {
+ my ( $self, @names_or_hashes ) = @_;
+ my @hashes = map { ref($_) ? $_ : { name => $_ } } @names_or_hashes;
+ return grep { !$self->name->can( $_->{name} ) } @hashes;
+}
+
+sub generate_delegation_list {
+ my ( $self, $delegation, $delegator_meta ) = @_;
+
+ if ( reftype($delegation) eq "CODE" ) {
+ return $delegation->( $self, $delegator_meta );
+ } elsif ( blessed($delegation) eq "Regexp" ) {
+ return grep { $_->{name} =~ /$delegation/ } $delegator_meta->compute_all_applicable_methods();
+ } else {
+ confess "The 'handles' specification '$delegation' is not supported";
+ }
+}
+
+sub _guess_attr_class_or_role {
+ my ( $self, $attr, $params ) = @_;
+
+ my ( $isa, $does ) = @{ $params }{qw/isa does/};
+
+ confess "Generative delegations must explicitly specify a class or a role for the attribute's type"
+ unless $isa || $does;
+
+ # if it's a class/role name make it into a meta object
+ for (grep { defined && !ref($_) } $isa, $does) {
+ confess "Generative delegations must refer to Moose class/role types"
+ unless $_->can("meta");
+ $_ = $_->meta;
+ }
+
+ for (grep { blessed($_) } $isa, $does) {
+ confess "You must use classes/roles, not type constraints to use delegation"
+ unless $_->isa( "Moose::Meta::Class" );
+ }
+
+ confess "Cannot have an isa option and a does option if the isa does not do the does"
+ if $isa && $does and !confess->does( $does );
+
+ return $isa || $does;
+}
sub add_override_method_modifier {
my ($self, $name, $method, $_super_package) = @_;
--- /dev/null
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 34;
+use Test::Exception;
+
+{
+
+ package ChildASuper;
+ use Moose;
+
+ sub child_a_super_method { "as" }
+
+ package ChildA;
+ use Moose;
+
+ extends "ChildASuper";
+
+ sub child_a_method_1 { "a1" }
+ sub child_a_method_2 { Scalar::Util::blessed($_[0]) . " a2" }
+
+ package ChildASub;
+ use Moose;
+
+ extends "ChildA";
+
+ sub child_a_method_3 { "a3" }
+
+ package ChildB;
+ use Moose;
+
+ sub child_b_method_1 { "b1" }
+ sub child_b_method_2 { "b2" }
+ sub child_b_method_3 { "b3" }
+
+ package ChildC;
+ use Moose;
+
+ sub child_c_method_1 { "c1" }
+ sub child_c_method_2 { "c2" }
+ sub child_c_method_3_la { "c3" }
+ sub child_c_method_4_la { "c4" }
+
+ package ChildD;
+ use Moose;
+
+ sub child_d_method_1 { "d1" }
+ sub child_d_method_2 { "d2" }
+
+ package ChildE;
+ # no Moose
+
+ sub new { bless {}, shift }
+ sub child_e_method_1 { "e1" }
+ sub child_e_method_2 { "e2" }
+
+ package ChildF;
+ # no Moose
+
+ sub new { bless {}, shift }
+ sub child_f_method_1 { "f1" }
+ sub child_f_method_2 { "f2" }
+
+ package Parent;
+ use Moose;
+
+ ::dies_ok {
+ has child_a => (
+ is => "ro",
+ default => sub { ChildA->new },
+ handles => all_methods,
+ );
+ } "all_methods requires explicit isa";
+
+ ::lives_ok {
+ has child_a => (
+ isa => "ChildA",
+ is => "ro",
+ default => sub { ChildA->new },
+ handles => all_methods,
+ );
+ } "allow all_methods with explicit isa";
+
+ ::lives_ok {
+ has child_b => (
+ default => sub { ChildB->new },
+ handles => [qw/child_b_method_1/],
+ );
+ } "don't need to declare isa if method list is predefined";
+
+ ::lives_ok {
+ has child_c => (
+ isa => "ChildC",
+ is => "ro",
+ default => sub { ChildC->new },
+ handles => qr/_la$/,
+ );
+ } "can declare regex collector";
+
+ ::dies_ok {
+ has child_d => (
+ is => "ro",
+ default => sub { ChildD->new },
+ handles => sub {
+ my ( $class, $delegate_class ) = @_;
+ }
+ );
+ } "can't create attr with generative handles parameter and no isa";
+
+ ::lives_ok {
+ has child_d => (
+ isa => "ChildD",
+ is => "ro",
+ default => sub { ChildD->new },
+ handles => sub {
+ my ( $class, $delegate_class ) = @_;
+ return;
+ }
+ );
+ } "can't create attr with generative handles parameter and no isa";
+
+ ::lives_ok {
+ has child_e => (
+ isa => "ChildE",
+ is => "ro",
+ default => sub { ChildE->new },
+ handles => "child_e_method_2",
+ );
+ } "can delegate to non moose class using explicit method list";
+
+ ::dies_ok {
+ has child_f => (
+ isa => "ChildF",
+ is => "ro",
+ default => sub { ChildF->new },
+ handles => sub { },
+ );
+ } "but not generative one";
+
+ sub parent_method { "p" }
+}
+
+# sanity
+
+isa_ok( my $p = Parent->new, "Parent" );
+isa_ok( $p->child_a, "ChildA" );
+#isa_ok( $p->child_b, "ChildB" ); # no accessor
+isa_ok( $p->child_c, "ChildC" );
+isa_ok( $p->child_d, "ChildD" );
+isa_ok( $p->child_e, "ChildE" );
+
+ok( !$p->can("child_b"), "no child b accessor" );
+ok( !$p->can("child_f"), "no child f" );
+
+is( $p->parent_method, "p", "parent method" );
+is( $p->child_a->child_a_super_method, "as", "child supermethod" );
+is( $p->child_a->child_a_method_1, "a1", "child method" );
+
+can_ok( $p, "child_a_super_method" );
+can_ok( $p, "child_a_method_1" );
+can_ok( $p, "child_a_method_2" );
+ok( !$p->can( "child_a_method_3" ), "but not subclass of delegate class" );
+
+is( $p->child_a_method_1, $p->child_a->child_a_method_1, "delegate behaves the same" );
+is( $p->child_a_method_2, "ChildA a2", "delegates are their own invocants" );
+
+
+can_ok( $p, "child_b_method_1" );
+ok( !$p->can("child_b_method_2"), "but not ChildB's unspecified siblings" );
+
+
+ok( !$p->can($_), "none of ChildD's methods ($_)" )
+ for grep { /^child/ } map { $_->{name} } ChildD->meta->compute_all_applicable_methods();
+
+can_ok( $p, "child_c_method_3_la" );
+can_ok( $p, "child_c_method_4_la" );
+
+is( $p->child_c_method_3_la, "c3", "ChildC method delegated OK" );
+
+can_ok( $p, "child_e_method_2" );
+ok( !$p->can("child_e_method_1"), "but not child_e_method_1");
+
+is( $p->child_e_method_2, "e2", "delegate to non moose class (child_e_method_2)" );