Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.
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.
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.
Great Class - was about to do something similar. (Score:1)
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
Re:Great Class - was about to do something similar (Score:2)
I'll consider this, but I have some questions about your needs. First, I had considered allowing this:
That raises the obvious question of "how will you throw the exception? My class makes no assumptions. Instead, it lets you do this:
Re:Great Class - was about to do something similar (Score:1)
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
Re:Great Class - was about to do something similar (Score:2)
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.
Re:Great Class - was about to do something similar (Score:1)
Re:Great Class - was about to do something similar (Score:2)
For Bricolage, we throw custom exceptions for localization reasons: we present the exception in the user's native language. However, there's no requirement to use custom exceptions with Class::BuildMethods. Just croak, die, or whatever you'd like to do.
Implementation-agnostic Method Generators (Score:1)
Several of the method-generators included with my Class::MakeMethods [cpan.org] have this capability. (Specifically, those that end in ::InsideOut or ::Inheritab