From: Stevan Little Date: Thu, 3 Jan 2008 06:34:31 +0000 (+0000) Subject: adding in method aliasing during composition X-Git-Tag: 0_35~37 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=3e19778d44d48d46fbbf0c32c10ee9b11371225d;p=gitmo%2FMoose.git adding in method aliasing during composition --- diff --git a/lib/Moose/Meta/Role/Application.pm b/lib/Moose/Meta/Role/Application.pm index 13da2c9..74abb33 100644 --- a/lib/Moose/Meta/Role/Application.pm +++ b/lib/Moose/Meta/Role/Application.pm @@ -13,6 +13,12 @@ __PACKAGE__->meta->add_attribute('method_exclusions' => ( default => sub { [] } )); +__PACKAGE__->meta->add_attribute('method_aliases' => ( + init_arg => 'alias', + reader => 'get_method_aliases', + default => sub { {} } +)); + sub new { my ($class, %params) = @_; @@ -34,6 +40,17 @@ sub is_method_excluded { return 0; } +sub is_method_aliased { + my ($self, $method_name) = @_; + exists $self->get_method_aliases->{$method_name} ? 1 : 0 +} + +sub is_aliased_method { + my ($self, $method_name) = @_; + my %aliased_names = reverse %{$self->get_method_aliases}; + exists $aliased_names{$method_name} ? 1 : 0; +} + sub apply { my $self = shift; @@ -90,6 +107,12 @@ This is the abstract base class for role applications. =item B +=item B + +=item B + +=item B + =item B =item B diff --git a/lib/Moose/Meta/Role/Application/ToClass.pm b/lib/Moose/Meta/Role/Application/ToClass.pm index ffb4673..f283ad7 100644 --- a/lib/Moose/Meta/Role/Application/ToClass.pm +++ b/lib/Moose/Meta/Role/Application/ToClass.pm @@ -42,7 +42,10 @@ sub check_required_methods { # the require methods stuff. foreach my $required_method_name ($role->get_required_method_list) { - unless ($class->find_method_by_name($required_method_name)) { + if (!$class->find_method_by_name($required_method_name)) { + + next if $self->is_aliased_method($required_method_name); + confess "'" . $role->name . "' requires the method '$required_method_name' " . "to be implemented by '" . $class->name . "'"; } @@ -111,6 +114,12 @@ sub apply_methods { next if $self->is_method_excluded($method_name); + my $orig_method_name = $method_name; + + if ($self->is_method_aliased($method_name)) { + $method_name = $self->get_method_aliases->{$method_name}; + } + # it if it has one already if ($class->has_method($method_name) && # and if they are not the same thing ... @@ -121,7 +130,7 @@ sub apply_methods { # add it, although it could be overriden $class->alias_method( $method_name, - $role->get_method($method_name) + $role->get_method($orig_method_name) ); } } diff --git a/lib/Moose/Meta/Role/Application/ToRole.pm b/lib/Moose/Meta/Role/Application/ToRole.pm index 23cfd38..afd1a44 100644 --- a/lib/Moose/Meta/Role/Application/ToRole.pm +++ b/lib/Moose/Meta/Role/Application/ToRole.pm @@ -34,6 +34,9 @@ sub check_role_exclusions { sub check_required_methods { my ($self, $role1, $role2) = @_; foreach my $required_method_name ($role1->get_required_method_list) { + + next if $self->is_aliased_method($required_method_name); + $role2->add_required_methods($required_method_name) unless $role2->find_method_by_name($required_method_name); } @@ -68,6 +71,12 @@ sub apply_methods { next if $self->is_method_excluded($method_name); + my $orig_method_name = $method_name; + + if ($self->is_method_aliased($method_name)) { + $method_name = $self->get_method_aliases->{$method_name}; + } + # it if it has one already if ($role2->has_method($method_name) && # and if they are not the same thing ... @@ -80,7 +89,7 @@ sub apply_methods { # add it, although it could be overriden $role2->alias_method( $method_name, - $role1->get_method($method_name) + $role1->get_method($orig_method_name) ); } } diff --git a/lib/Moose/Spec/Role.pod b/lib/Moose/Spec/Role.pod index 16bf475..b44667a 100644 --- a/lib/Moose/Spec/Role.pod +++ b/lib/Moose/Spec/Role.pod @@ -72,7 +72,7 @@ just like attributes and the C keyword). =head2 Role Composition -=head3 Composing into a Role +=head3 Composing into a Class =over 4 @@ -92,7 +92,9 @@ just like attributes and the C keyword). =back -=head3 Composing into a Class +=head3 Composing into a Instance + +=head3 Composing into a Role =over 4 @@ -112,9 +114,7 @@ just like attributes and the C keyword). =back -=head3 Composing into a Instance - -=head2 Role Summation +=head3 Role Summation When multiple roles are added to another role (using the C keyword) the roles are composed symmetrically. @@ -196,6 +196,74 @@ is the key. =back +=head3 Composition Edge Cases + +This is a just a set of complex edge cases which can easily get +confused. This attempts to clarify those cases and provide an +explination of what is going on in them. + +=over 4 + +=item Role Method Overriding + +Many people want to "override" methods in roles they are consuming. +This works fine for classes, since the local class method is favored +over the role method. However in roles it is trickier, this is because +conflicts result in neither method being chosen and the method being +"required" instead. + +Here is an example of this (incorrect) type of overriding. + + package Role::Foo; + use Moose::Role; + + sub foo { ... } + + package Role::FooBar; + use Moose::Role; + + with 'Role::Foo'; + + sub foo { ... } + sub bar { ... } + +Here the C methods conflict and the Role::FooBar now requires a +class or role consuming it to implement C. This is very often not +what the user wants. + +Now here is an example of the (correct) type of overriding, only it is +not overriding at all, as is explained in the text below. + + package Role::Foo; + use Moose::Role; + + sub foo { ... } + + package Role::Bar; + use Moose::Role; + + sub foo { ... } + sub bar { ... } + + package Role::FooBar; + use Moose::Role; + + with 'Role::Foo', 'Role::Bar'; + + sub foo { ... } + +This works because the combination of Role::Foo and Role::Bar produce +a conflict with the C method. This conflict results in the +composite role (that was created by the combination of Role::Foo +and Role::Bar using the I keyword) having a method requirement +of C. The Role::FooBar then fufills this requirement. + +It is important to note that Role::FooBar is simply fufilling the +required C method, and **NOT** overriding C. This is an +important distinction to make. + +=back + =head1 SEE ALSO =over 4 diff --git a/t/030_roles/013_method_aliasing_during_composition.t b/t/030_roles/013_method_aliasing_during_composition.t new file mode 100644 index 0000000..59130e6 --- /dev/null +++ b/t/030_roles/013_method_aliasing_during_composition.t @@ -0,0 +1,52 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More no_plan => 1; +use Test::Exception; + +BEGIN { + use_ok('Moose'); +} + +{ + package My::Role; + use Moose::Role; + + sub foo { 'Foo::foo' } + sub bar { 'Foo::bar' } + sub baz { 'Foo::baz' } + + requires 'role_bar'; + + package My::Class; + use Moose; + + ::lives_ok { + with 'My::Role' => { alias => { bar => 'role_bar' } }; + } '... this succeeds'; +} + +ok(My::Class->meta->has_method($_), "we have a $_ method") for qw(foo baz role_bar); +ok(!My::Class->meta->has_method('bar'), '... but we dont get bar'); + +{ + package My::OtherRole; + use Moose::Role; + + ::lives_ok { + with 'My::Role' => { alias => { bar => 'role_bar' } }; + } '... this succeeds'; + + sub bar { 'My::OtherRole::bar' } +} + +ok(My::OtherRole->meta->has_method($_), "we have a $_ method") for qw(foo bar baz role_bar); +ok(!My::OtherRole->meta->requires_method('bar'), '... and the &bar method is not required'); +ok(!My::OtherRole->meta->requires_method('role_bar'), '... and the &role_bar method is not required'); + + + + + diff --git a/t/030_roles/020_role_composite.t b/t/030_roles/020_role_composite.t index ae6d88c..76db467 100644 --- a/t/030_roles/020_role_composite.t +++ b/t/030_roles/020_role_composite.t @@ -20,7 +20,10 @@ BEGIN { use Moose::Role; package Role::Baz; - use Moose::Role; + use Moose::Role; + + package Role::Gorch; + use Moose::Role; } { @@ -50,5 +53,31 @@ BEGIN { lives_ok { Moose::Meta::Role::Application::RoleSummation->new->apply($c); - } '... this composed okay'; + } '... this composed okay'; + + ##... now nest 'em + { + my $c2 = Moose::Meta::Role::Composite->new( + roles => [ + $c, + Role::Gorch->meta, + ] + ); + isa_ok($c2, 'Moose::Meta::Role::Composite'); + + is($c2->name, 'Role::Foo|Role::Bar|Role::Baz|Role::Gorch', '... got the composite role name'); + + is_deeply($c2->get_roles, [ + $c, + Role::Gorch->meta, + ], '... got the right roles'); + + ok($c2->does_role($_), '... our composite does the role ' . $_) + for qw( + Role::Foo + Role::Bar + Role::Baz + Role::Gorch + ); + } }