Class::MOP - all the method methods and tests
Stevan Little [Wed, 25 Jan 2006 22:25:27 +0000 (22:25 +0000)]
lib/Class/MOP.pm
lib/Class/MOP/Class.pm
t/003_methods.t
t/004_advanced_methods.t [new file with mode: 0644]
t/010_self_introspection.t [new file with mode: 0644]

index cadef21..2b86f1a 100644 (file)
@@ -280,6 +280,11 @@ with the following information; method name (which will always be the
 same as C<$method_name>), the name of the class in which the method 
 lives and a CODE reference for the actual method.
 
+The list of methods produced is a distinct list, meaning there are no 
+duplicates in it. This is especially useful for things like object 
+initialization and destruction where you only want the method called 
+once, and in the correct order.
+
 =back
 
 =head3 Attributes
index 79b267c..2f600dc 100644 (file)
@@ -92,6 +92,7 @@ sub add_method {
     my ($self, $method_name, $method) = @_;
     (defined $method_name && $method_name)
         || confess "You must define a method name";
+    # use reftype here to allow for blessed subs ...
     (reftype($method) && reftype($method) eq 'CODE')
         || confess "Your code block must be a CODE reference";
     my $full_method_name = ($self->name . '::' . $method_name);    
@@ -151,9 +152,82 @@ sub remove_method {
 sub get_method_list {
     my $self = shift;
     no strict 'refs';
-    grep { 
-        defined &{$self->name . '::' . $_} && $self->has_method($_) 
-    } %{$self->name . '::'};
+    grep { $self->has_method($_) } %{$self->name . '::'};
+}
+
+sub compute_all_applicable_methods {
+    my $self = shift;
+    my @methods;
+    # keep a record of what we have seen
+    # here, this will handle all the 
+    # inheritence issues because we are 
+    # using the &class_precedence_list
+    my (%seen_class, %seen_method);
+    foreach my $class ($self->class_precedence_list()) {
+        next if $seen_class{$class};
+        $seen_class{$class}++;
+        # fetch the meta-class ...
+        my $meta = $self->initialize($class);
+        foreach my $method_name ($meta->get_method_list()) { 
+            next if exists $seen_method{$method_name};
+            $seen_method{$method_name}++;
+            push @methods => {
+                name  => $method_name, 
+                class => $class,
+                code  => $meta->get_method($method_name)
+            };
+        }
+    }
+    return @methods;
+}
+
+## Recursive Version of compute_all_applicable_methods
+# sub compute_all_applicable_methods {
+#     my ($self, $seen) = @_;
+#     $seen ||= {};
+#     (
+#         (map { 
+#             if (exists $seen->{$_}) { 
+#                 ();
+#             }
+#             else {
+#                 $seen->{$_}++;
+#                 {
+#                     name  => $_, 
+#                     class => $self->name,
+#                     code  => $self->get_method($_)
+#                 };
+#             }
+#         } $self->get_method_list()),
+#         map { 
+#             $self->initialize($_)->compute_all_applicable_methods($seen)
+#         } $self->superclasses()
+#     );
+# }
+
+sub find_all_methods_by_name {
+    my ($self, $method_name) = @_;
+    (defined $method_name && $method_name)
+        || confess "You must define a method name to find";    
+    my @methods;
+    # keep a record of what we have seen
+    # here, this will handle all the 
+    # inheritence issues because we are 
+    # using the &class_precedence_list
+    my %seen_class;
+    foreach my $class ($self->class_precedence_list()) {
+        next if $seen_class{$class};
+        $seen_class{$class}++;
+        # fetch the meta-class ...
+        my $meta = $self->initialize($class);
+        push @methods => {
+            name  => $method_name, 
+            class => $class,
+            code  => $meta->get_method($method_name)
+        } if $meta->has_method($method_name);
+    }
+    return @methods;
+
 }
 
 1;
index 24dde7a..0bfe252 100644 (file)
@@ -5,7 +5,6 @@ use warnings;
 
 use Test::More no_plan => 1;
 use Test::Exception;
-use Test::Deep;
 
 BEGIN {
     use_ok('Class::MOP');   
@@ -83,6 +82,29 @@ is_deeply(
     [ qw(FOO_CONSTANT bang bar baz blah bling evaled_foo floob foo) ],
     '... got the right method list for Foo');
 
+is_deeply(
+    [ sort { $a->{name} cmp $b->{name} } $Foo->compute_all_applicable_methods() ],
+    [
+        map {
+            {
+            name  => $_,
+            class => 'Foo',
+            code  => $Foo->get_method($_) 
+            }
+        } qw(
+            FOO_CONSTANT
+            bang 
+            bar 
+            baz 
+            blah 
+            bling 
+            evaled_foo 
+            floob 
+            foo
+        )
+    ],
+    '... got the right list of applicable methods for Foo');
+
 is($Foo->remove_method('foo'), $foo, '... removed the foo method');
 ok(!$Foo->has_method('foo'), '... !Foo->has_method(foo) we just removed it');
 dies_ok { Foo->foo } '... cannot call Foo->foo because it is not there';
@@ -129,4 +151,40 @@ is(Bar->foo, 'Bar::foo v2', '... Bar->foo == "Bar::foo v2"');
 is_deeply(
     [ sort $Bar->get_method_list ],
     [ qw(bar foo) ],
-    '... got the right method list for Bar');
+    '... got the right method list for Bar');  
+    
+is_deeply(
+    [ sort { $a->{name} cmp $b->{name} } $Bar->compute_all_applicable_methods() ],
+    [
+        {
+            name  => 'bang',
+            class => 'Foo',
+            code  => $Foo->get_method('bang') 
+        },
+        {
+            name  => 'bar',
+            class => 'Bar',
+            code  => $Bar->get_method('bar')            
+        },
+        (map {
+            {
+                name  => $_,
+                class => 'Foo',
+                code  => $Foo->get_method($_) 
+            }
+        } qw(        
+            baz 
+            blah 
+            bling 
+            evaled_foo 
+            floob 
+        )),
+        {
+            name  => 'foo',
+            class => 'Bar',
+            code  => $Bar->get_method('foo')            
+        },        
+    ],
+    '... got the right list of applicable methods for Bar');
+
+
diff --git a/t/004_advanced_methods.t b/t/004_advanced_methods.t
new file mode 100644 (file)
index 0000000..f3a3d49
--- /dev/null
@@ -0,0 +1,225 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More no_plan => 1;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Class::MOP');   
+    use_ok('Class::MOP::Class');        
+}
+
+=pod
+
+The following class hierarhcy is very contrived 
+and totally horrid (it won't work under C3 even),
+but it tests a number of aspect of this module.
+
+A more real-world example would be a nice addition :)
+
+=cut
+
+{
+    package Foo;
+    
+    sub BUILD { 'Foo::BUILD' }    
+    sub foo { 'Foo::foo' }
+    
+    package Bar;
+    our @ISA = ('Foo');
+    
+    sub BUILD { 'Bar::BUILD' }    
+    sub bar { 'Bar::bar' }     
+    
+    package Baz;
+    our @ISA = ('Bar');
+    
+    sub BUILD { 'Baz::BUILD' }    
+    sub baz { 'Baz::baz' }
+    sub foo { 'Baz::foo' }           
+    
+    package Foo::Bar;
+    our @ISA = ('Foo', 'Bar');
+    
+    sub BUILD { 'Foo::Bar::BUILD' }    
+    sub foobar { 'Foo::Bar::foobar' }    
+    
+    package Foo::Bar::Baz;
+    our @ISA = ('Foo', 'Bar', 'Baz');
+    
+    sub BUILD { 'Foo::Bar::Baz::BUILD' }    
+    sub bar { 'Foo::Bar::Baz::bar' }    
+    sub foobarbaz { 'Foo::Bar::Baz::foobarbaz' }    
+}
+
+is_deeply(
+    [ sort { $a->{name} cmp $b->{name} } Class::MOP::Class->initialize('Foo')->compute_all_applicable_methods() ],
+    [
+        {
+            name  => 'BUILD',
+            class => 'Foo',
+            code  => \&Foo::BUILD 
+        },    
+        {
+            name  => 'foo',
+            class => 'Foo',
+            code  => \&Foo::foo 
+        },       
+    ],
+    '... got the right list of applicable methods for Foo');
+    
+is_deeply(
+    [ sort { $a->{name} cmp $b->{name} } Class::MOP::Class->initialize('Bar')->compute_all_applicable_methods() ],
+    [
+        {
+            name  => 'BUILD',
+            class => 'Bar',
+            code  => \&Bar::BUILD 
+        },    
+        {
+            name  => 'bar',
+            class => 'Bar',
+            code  => \&Bar::bar  
+        },
+        {
+            name  => 'foo',
+            class => 'Foo',
+            code  => \&Foo::foo  
+        },       
+    ],
+    '... got the right list of applicable methods for Bar');
+    
+
+is_deeply(
+    [ sort { $a->{name} cmp $b->{name} } Class::MOP::Class->initialize('Baz')->compute_all_applicable_methods() ],
+    [
+        {
+            name  => 'BUILD',
+            class => 'Baz',
+            code  => \&Baz::BUILD 
+        },    
+        {
+            name  => 'bar',
+            class => 'Bar',
+            code  => \&Bar::bar  
+        },
+        {
+            name  => 'baz',
+            class => 'Baz',
+            code  => \&Baz::baz  
+        },        
+        {
+            name  => 'foo',
+            class => 'Baz',
+            code  => \&Baz::foo  
+        },       
+    ],
+    '... got the right list of applicable methods for Baz');
+
+is_deeply(
+    [ sort { $a->{name} cmp $b->{name} } Class::MOP::Class->initialize('Foo::Bar')->compute_all_applicable_methods() ],
+    [
+        {
+            name  => 'BUILD',
+            class => 'Foo::Bar',
+            code  => \&Foo::Bar::BUILD 
+        },    
+        {
+            name  => 'bar',
+            class => 'Bar',
+            code  => \&Bar::bar  
+        },
+        {
+            name  => 'foo',
+            class => 'Foo',
+            code  => \&Foo::foo  
+        },       
+        {
+            name  => 'foobar',
+            class => 'Foo::Bar',
+            code  => \&Foo::Bar::foobar  
+        },        
+    ],
+    '... got the right list of applicable methods for Foo::Bar');
+
+is_deeply(
+    [ sort { $a->{name} cmp $b->{name} } Class::MOP::Class->initialize('Foo::Bar::Baz')->compute_all_applicable_methods() ],
+    [
+        {
+            name  => 'BUILD',
+            class => 'Foo::Bar::Baz',
+            code  => \&Foo::Bar::Baz::BUILD 
+        },    
+        {
+            name  => 'bar',
+            class => 'Foo::Bar::Baz',
+            code  => \&Foo::Bar::Baz::bar  
+        },
+        {
+            name  => 'baz',
+            class => 'Baz',
+            code  => \&Baz::baz  
+        },        
+        {
+            name  => 'foo',
+            class => 'Foo',
+            code  => \&Foo::foo  
+        },   
+        {
+            name  => 'foobarbaz',
+            class => 'Foo::Bar::Baz',
+            code  => \&Foo::Bar::Baz::foobarbaz  
+        },            
+    ],
+    '... got the right list of applicable methods for Foo::Bar::Baz');
+
+## find_all_methods_by_name
+
+is_deeply(
+    [ Class::MOP::Class->initialize('Foo::Bar')->find_all_methods_by_name('BUILD') ],
+    [
+        {
+            name  => 'BUILD',
+            class => 'Foo::Bar',
+            code  => \&Foo::Bar::BUILD 
+        },    
+        {
+            name  => 'BUILD',
+            class => 'Foo',
+            code  => \&Foo::BUILD 
+        },    
+        {
+            name  => 'BUILD',
+            class => 'Bar',
+            code  => \&Bar::BUILD 
+        }
+    ],
+    '... got the right list of BUILD methods for Foo::Bar');
+
+is_deeply(
+    [ Class::MOP::Class->initialize('Foo::Bar::Baz')->find_all_methods_by_name('BUILD') ],
+    [
+        {
+            name  => 'BUILD',
+            class => 'Foo::Bar::Baz',
+            code  => \&Foo::Bar::Baz::BUILD 
+        },    
+        {
+            name  => 'BUILD',
+            class => 'Foo',
+            code  => \&Foo::BUILD 
+        },    
+        {
+            name  => 'BUILD',
+            class => 'Bar',
+            code  => \&Bar::BUILD 
+        },    
+        {
+            name  => 'BUILD',
+            class => 'Baz',
+            code  => \&Baz::BUILD 
+        },        
+    ],
+    '... got the right list of BUILD methods for Foo::Bar::Baz');
\ No newline at end of file
diff --git a/t/010_self_introspection.t b/t/010_self_introspection.t
new file mode 100644 (file)
index 0000000..03037d5
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More no_plan => 1;
+use Test::Exception;
+
+BEGIN {
+    use_ok('Class::MOP::Class');        
+}
+
+
+my $meta = Class::MOP::Class->initialize('Class::MOP::Class');
+isa_ok($meta, 'Class::MOP::Class');
+
+foreach my $method_name (qw(
+    initialize create
+    name version
+    superclasses class_precedence_list
+    has_method get_method add_method remove_method 
+    get_method_list compute_all_applicable_methods find_all_methods_by_name
+    )) {
+    ok($meta->has_method($method_name), '... Class::MOP::Class->has_method(' . $method_name . ')');
+    {
+        no strict 'refs';
+        is($meta->get_method($method_name), 
+           \&{'Class::MOP::Class::' . $method_name},
+           '... Class::MOP::Class->get_method(' . $method_name . ') == &Class::MOP::Class::' . $method_name);        
+    }
+}
+
+is($meta->name, 'Class::MOP::Class', '... Class::MOP::Class->name');
+is($meta->version, $Class::MOP::Class::VERSION, '... Class::MOP::Class->version');
+
+is_deeply(
+    [ $meta->superclasses ], 
+    [], 
+    '... Class::MOP::Class->superclasses == []');
+    
+is_deeply(
+    [ $meta->class_precedence_list ], 
+    [ 'Class::MOP::Class' ], 
+    '... Class::MOP::Class->class_precedence_list == []');
+