diotalevi has asked for the wisdom of the Perl Monks concerning the following question:

RFC Class::AppendMethods

In response to Installing chained methods I created Class::AppendMethods. I'd like to get any general feedback on the module prior to uploading it to CPAN. I'm requesting any suggestions regarding API, coding style or the method used to solve this problem (or even the module name if you feel like it). The module consists of a single subroutine which has the job of either copying a code reference into a slot or if something is already present - installing a wrapper to ensure both the new and old methods are called.

Class::AppendMethods

package Class::AppendMethods; use strict; use warnings; use vars qw(@ISA @EXPORT_OK $VERSION %METHODS); require Exporter; @ISA = qw(Exporter); @EXPORT_OK = qw(append_method); $VERSION = '0.01'; sub append_method { my $method_name = shift; my $method_to_install = shift; no strict 'refs'; no warnings 'redefine'; # If I was given a method name then fetch the code # reference from the named slot unless (ref $method_to_install ) { # symref $method_to_install = \&{$method_to_install}; } # Track the list of references to install unless (exists $METHODS{$method_name}) { $METHODS{$method_name} = []; } my $pre_existing_methods = $METHODS{$method_name}; push @$pre_existing_methods, $method_to_install; # Install the new methods if (*{$method_name}{CODE}) { # If the pre-existing method isn't in the local cache then cop +y it over # first. unless (grep $_ == *{$method_name}{CODE}, @$pre_existing_metho +ds) { unshift @$pre_existing_methods, *{$method_name}{CODE}; } # Already existing method *{$method_name} = sub { $_[0]->$_( @_[1 .. $#_] ) for @$pre_existing_methods; }; } else { # Single method - no special calling *{$method_name} = $method_to_install; } # Return the method as a convenience (for who knows what, I don't +know) return *{$method_name}{CODE}; } 1; __END__ =head1 NAME Class::AppendMethods - Install multiple methods into a single slot =head1 SYNOPSIS use Class::AppendMethods 'append_method'; # This installs both versioning_hook and auditing_hook into the # method Object::pre_insert. for my $hook (qw(versioning auditing)) { append_method( 'Object::pre_insert', "${hook}_hook" ); } sub versioning_hook { ... } sub auditing_hook { ... } =head1 DESCRIPTION This allows you to install more than one method into a single method n +ame. I created this so I could install both versioning and auditing hooks i +nto another module's object space. So instead of creating a single larger +method which incorporates the functionality of both hooks I created C<Class::AppendMethods::append_method> to install a wrapper method as +needed. If only one method is ever installed into a space, it is installed dir +ectly with no wrapper. If you install more than one then C<append_method> cr +eates a wrapper which calls each of the specified methods in turn. =head1 PUBLIC METHODS =over 4 =item append_method append_method( $method_name, $method ); This function takes two parameters - the fully qualified name of the m +ethod to install into and the method to install. C<$method_name> must be the fully qualified method name. This means th +at for the method C<pre_insert> of a C<Foo::Bar> object you must pass in C<'Foo::Bar::pre_insert'>. C<$method> may be either a code reference or the fully qualified name +of the method to use. =back =head2 EXAMPLES =over 4 =item Example 1 use Class::AppendMethods 'append_method'; # This installs both versioning_hook and auditing_hook into the # method Object::pre_insert. for my $hook (qw(versioning auditing)) { append_method( 'Object::pre_insert', "${hook}_hook" ); } sub versioning_hook { ... } sub auditing_hook { ... } =item Example 2 use Class::AppendMethods 'append_method'; my @versioned_tables = ( .... ); my @audited_tables = ( .... ); for my $table_list ( { tables => \ @versioned_tables, prefix => 'versioned' }, { tables => \ @audited_tables, prefix => 'audited' } ) { my $tables = $table_list->{'tables'}; my $prefix = $table_list->{'prefix'}; for my $table ( @$tables ) { for my $hook ( qw[pre_insert pre_update pre_delete]) { my $method_name = "GreenPartyDB::Database::${table}::${ho +ok}"; my $method_inst = __PACKAGE__ . "::${prefix}_${hook}"; append_method( $method_name, $method_inst ); } } } sub versioned_pre_insert { ... } sub versioned_pre_update { ... } sub versioned_pre_delete { ... } sub audited_pre_insert { ... } sub audited_pre_update { ... } sub audited_pre_delete { ... } =back =head2 EXPORT This class optionally exports the C<append_method> function. =cut

Replies are listed 'Best First'.
Re: RFC Class::AppendMethods
by DrManhattan (Chaplain) on Apr 22, 2003 at 08:27 UTC
    Neat! A couple of suggestions: First, an insert_method() would be a useful addition to append_method(). I'd like to be able to add a subroutine that executes before an existing one instead of after. Also, your example should probably mention that the package of the method to append needs to be specified explicitly when passed as a string or it ends up in Class::AppendMethod and fails to exist. E.g.
    #!/usr/bin/perl use strict; package Dummy; sub test { print "one\n"; } package main; use Class::AppendMethods 'append_method'; append_method("Dummy::test", "post_test"); sub post_test { print "two\n"; } Dummy::test;
    Gives me this output:
    one Undefined subroutine &Class::AppendMethods::post_test called at /home/ +matt/Class/AppendMethods.pm line 43.
    Whereas
    append_method("Dummy::test", "main::post_test");
    or
    append_method("Dummy::test", \&post_test);
    ... behaves as expected.

    -Matt

Re: RFC Class::AppendMethods
by PodMaster (Abbot) on Apr 22, 2003 at 08:43 UTC
    It just hit me, couldn't you do what what Class::AppendMethods does with Class::Delegation by TheDamian?


    MJD says you can't just make shit up and expect the computer to know what you mean, retardo!
    I run a Win32 PPM repository for perl 5.6x+5.8x. I take requests.
    ** The Third rule of perl club is a statement of fact: pod is sexy.

      Yes.... it looks like it might be possible. I didn't know about C::D's multiple method calling functionality. Instead of calling append_method I can use C::D (assuming I know all the methods I want to use at onece, build a bit of perl code and then eval that. I'll keep this in mind but I don't think this is any easier. Thanks PodMaster.

      my @methods_q = map "sub { \$_[0]->GreenPartyDB::Database::$method( \@ +_[ 1 .. \$#_ ] ) }", @methods; my $method_c = join ", ", @method_c; eval qq[ package GreenPartyDB::Database::${table}; use Class::Delegation send => '$hook', to => [ $method_c ];
Re: RFC Class::AppendMethods
by diotalevi (Canon) on Apr 22, 2003 at 06:14 UTC

    I should add that I mean for this code meant to be good enough for you to use it for your own production code. Mostly I want to use it for myself but I figure that if it can make it past the gauntlet here then its likely ok. So please, evaluate this like you would potential production code for your system.