adding in method aliasing during composition
Stevan Little [Thu, 3 Jan 2008 06:34:31 +0000 (06:34 +0000)]
lib/Moose/Meta/Role/Application.pm
lib/Moose/Meta/Role/Application/ToClass.pm
lib/Moose/Meta/Role/Application/ToRole.pm
lib/Moose/Spec/Role.pod
t/030_roles/013_method_aliasing_during_composition.t [new file with mode: 0644]
t/030_roles/020_role_composite.t

index 13da2c9..74abb33 100644 (file)
@@ -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<is_method_excluded>
 
+=item B<get_method_aliases>
+
+=item B<is_aliased_method>
+
+=item B<is_method_aliased>
+
 =item B<apply>
 
 =item B<check_role_exclusions>
index ffb4673..f283ad7 100644 (file)
@@ -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)
             );
         }
     }
index 23cfd38..afd1a44 100644 (file)
@@ -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)
             );
         }
     }
index 16bf475..b44667a 100644 (file)
@@ -72,7 +72,7 @@ just like attributes and the C<override> 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<override> 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<override> keyword).
 
 =back
 
-=head3 Composing into a Instance  
-
-=head2 Role Summation
+=head3 Role Summation
 
 When multiple roles are added to another role (using the 
 C<with @roles> 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<foo> methods conflict and the Role::FooBar now requires a 
+class or role consuming it to implement C<foo>. 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<foo> method. This conflict results in the 
+composite role (that was created by the combination of Role::Foo 
+and Role::Bar using the I<with> keyword) having a method requirement 
+of C<foo>. The Role::FooBar then fufills this requirement. 
+
+It is important to note that Role::FooBar is simply fufilling the 
+required C<foo> method, and **NOT** overriding C<foo>. 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 (file)
index 0000000..59130e6
--- /dev/null
@@ -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');
+
+
+
+
+
index ae6d88c..76db467 100644 (file)
@@ -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                        
+            );     
+    }
 }