Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Wednesday November 23, 2005
03:41 PM

Class::BuildMethods

[ #27712 ]

Though I was not happy with the thought of adding another module to the CPAN Class:: hierarchy, I did. It's essentially a poor man's MethodMaker. Here are the features I needed in the order I needed them.

  1. It's implementation agnostic. You can used a blessed typeglob for all I care (most modules which build your methods assume you're using a specific type of blessed reference).
  2. Super flexible and optional data validation.
  3. Easy to specify default values.
  4. Very lightweight.

Note that the first item on my list was the most important yet I couldn't find any modules in this namespace which respected that requirement. I'm sure they're there, but where?

NAME
    Class::BuildMethods - Lightweight implementation-agnostic generic
    methods.

VERSION
    Version 0.10

SYNOPSIS
        use Class::BuildMethods
            'name',
            rank => { default  => 'private' },
            date => { validate => \&valid_date };

DESCRIPTION
    This class allows you to quickly add simple getter/setter methods to
    your classes with optional default values and validation. We assume no
    implementation for your class, so you may use a standard blessed
    hashref, blessed arrayref, inside-out objects, etc. This module does not
    alter anything about your class aside from installing requested methods
    and optionally adding a "DESTROY" method. See "CLEANING UP" for more
    information, particularly the "destroy" method.

BASIC METHODS
     package Foo;
     use Class::BuildMethods qw/name rank/;

     sub new {
       ... whatever implementation you need
     }

     # later

     my $foo = Foo->new;
     $foo->name('bob');
     print $foo->name;   # prints 'bob'

    Using a simple list with "Class::BuildMethods" adds those methods as
    getters/setters to your class.

    Note that when using a method as a setter, you may only pass in a single
    value. Arrays and hashes should be passed by reference.

DEFAULT VALUES
     package Foo;
     use Class::BuildMethods
       'name',
       rank => { default => 'private' };

     # later

     my $foo = Foo->new;
     print $foo->rank;   # prints 'private'
     $foo->rank('corporal');
     print $foo->rank;   # prints 'corporal'

    After any method name passed to "Class::BuildMethods", you may pass it a
    hash reference of constraints. If a key of "default" is found, the value
    for that key will be assigned as the default value for the method.

VALIDATION
     package Drinking::Buddy;
     use Class::BuildMethods;
       'name',
       age => {
         validate => sub {
            my ($self, $age) = @_;
            die "Too young" if $age < 21;
         }
       };

     # later

     my $bubba = Drinking::Buddy->new;
     $bubba->age(18);   # fatal error
     $bubba->age(21);   # Works
     print $bubba->age; # prints '21'

    If a key of "validate" is found, a subroutine is expected as the next
    argument. When setting a value, the subroutine will be called with the
    invocant as the first argument and the new value as the second argument.
    You may supply any code you wish to enforce validation.

ADDING METHODS AT RUNTIME
  build
      Class::BuildMethods->build(
        'name',
        rank => { default => 'private' }
      );

    This allows you to add the methods at runtime. Takes the same arguments
    as the import list to the class.

CLEANING UP
  destroy
      Class::BuildMethods->destroy($instance);

    This method destroys instance data for the instance supplied.

    Ordinarily you should never have to call this as a "DESTROY" method is
    installed in your namespace which does this for you. However, if you
    need a custom destroy method, provide the special "[NO_DESTROY]" token
    to "Class::BuildMethods" when you're creating it.

     use Class::BuildMethods qw(
        name
        rank
        serial
        [NO_DESTROY]
     );

     sub DESTROY {
       my $self shift;
       # whatever cleanup code you need
       Class::BuildMethods->destroy($self);
     }

  reset
      Class::BuildMethods->reset;   # assumes current package
      Class::BuildMethods->reset($package);

    This methods deletes all of the values for the methods added by
    "Class::BuildMethods". Any methods with default values will now have
    their default values restored. It does not remove the methods. Returns
    the number of methods reset.

  reclaim
      Class::BuildMethods->reclaim;   # assumes current package
      Class::BuildMethods->reclaim($package);

    Like "reset" but more final. Removes any values set for methods, any
    default values and pretty much any trace of a given module from this
    package. It does not remove the methods. Any attempt to use the the
    autogenerated methods after this method is called is not guaranteed.

  packages
      my @packages = Class::BuildMethods->packages;

    Returns a sorted list of packages for which methods have been built. If
    "reclaim" has been called for a package, this method will not return
    that package. This is generally useful if you need to do a global code
    cleanup from a remote package:

     foreach my $package (Class::BuildMethods->packages) {
        Class::BuildMethods->reclaim($package);
     }
     # then whatever teardown you need

    In reality, you probably will never need this method.

CAVEATS
    Some people will not be happy that if they need to store an array or a
    hash they must pass them by reference as each generated method expects a
    single value to be passed in when used as a "setter". This is because
    this module is designd to be *simple*. It's very lightweight and very
    fast.

AUTHOR
    Curtis "Ovid" Poe, "<ovid@cpan.org>"

ACKNOWLEDGEMENTS
    Thanks to Kineticode, Inc. for supporting development of this package.

BUGS
    Please report any bugs or feature requests to
    "bug-class-buildmethods@rt.cpan.org", or through the web interface at
    <http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Class-BuildMethods>. I
    will be notified, and then you'll automatically be notified of progress
    on your bug as I make changes.

COPYRIGHT & LICENSE
    Copyright 2005 Curtis "Ovid" Poe, all rights reserved.

    This program is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • Yo Ovid,

    I have been working with one of my coworkers at Yahoo! on the design of a class very much like the one you just wrote, having many of the same issues with the existing stuff on CPAN.  I really like what you've done, and we may just end up using your classes, but we have a few things we'd like to add.  I'll list them here so you can tell me if they make sense.  If not, I'll be happy to explain my reasoning, or let you try to change my mind.

    My Wishlist:

    - A 'description' attribute for me
    • I'll consider this, but I have some questions about your needs. First, I had considered allowing this:

      foo => $RE{num}{real},

      That raises the obvious question of "how will you throw the exception? My class makes no assumptions. Instead, it lets you do this:

      sub validate_re (
        my ($re, $message) = shift;
        return sub {
          my ($class, $value) = @_;
          throw_custom_exception $message unless $value =~ $re;
        }
      }

      Class::BuildMethods->build(
        foo => {
       

      • Great point: a coderef will probably support most use cases.

        However, I don't see much need to allow the user to customize exceptions.  Why would you need anything but croak "$value is not a $description"?

        Even if you think this is crucial...consider an alternative interface where validate subroutines just return true or false, and the 'description' parameter defines the exception message by default.  The disadvantage is that you now have to define your accessor with both a 'validate' property and a
        • However, I don't see much need to allow the user to customize exceptions. Why would you need anything but croak "$value is not a $description"?

          Well, in my team we use the Error module for all Exceptions. The exception above should probably be something like Project::Exceptions::Validation::Accessor::MyField, which would be a sub class of a more general exception.

          • But you surely don't require that all modules used in your project throw a special type of Exception. It's great if you write your own modules to use more sophisticated exceptions, but you should be able to use modules that thows standard Perl text exceptions. There's no reason that we need to treat this class any differently than any other class that throws exceptions.
  • It's implementation agnostic. You can used a blessed typeglob for all I care (most modules which build your methods assume you're using a specific type of blessed reference). ... Note that the first item on my list was the most important yet I couldn't find any modules in this namespace which respected that requirement. I'm sure they're there, but where?

    Several of the method-generators included with my Class::MakeMethods [cpan.org] have this capability. (Specifically, those that end in ::InsideOut or ::Inheritab