Jim Thomason > Basset-1.04 > Basset::Object

Download:
Basset-1.04.tar.gz

Dependencies

Annotate this POD

CPAN RT

New  1
Open  0
View/Report Bugs
Module Version: 1.03   Source  

NAME ^

Basset::Object - used to create objects

AUTHOR ^

Jim Thomason, jim@jimandkoka.com

DESCRIPTION ^

This is my ultimate object creation toolset to date. It has roots in Mail::Bulkmail, Text::Flowchart, and the unreleased abstract object constructors that I've tooled around with in the past.

If you want an object to be compatible with anything else I've written, then subclass it off of here.

Of course, you don't have to use this to create subclasses, but you'll run the risk of making something with an inconsistent interface vs. the rest of the system. That'll confuse people and make them unhappy. So I recommend subclassing off of here to be consistent. Of course, you may not like these objects, but they do work well and are consistent. Consistency is very important in interface design, IMHO.

Please read the tutorials at http://www.bassetsoftware.com/perl/basset/tutorial/.

METHODS ^

add_attr

add_attr adds object attributes to the class.

Okay, now we're going to get into some philosophy. First of all, let me state that I *love* Perl's OO implementation. I usually get smacked upside the head when I say that, but I find it really easy to use, work with, manipulate, and so on. And there are things that you can do in Perl's OO that you can't in Java or C++ or the like. Perl, for example, can have *totally* private values that are completely inaccessible (lexicals, natch). private vars in the other languages can be redefined or tweaked or subclassed or otherwise gotten around in some form. Not Perl.

And I obviously just adore Perl anyway. I get funny looks when I tell people that I like perl so much because it works the way I think. That bothers people for some reason.

Anyway, as much as I like how it works, I don't like the fact that there's no consistent object type. An object is, of course, a blessed ((thingie)) (scalar, array, code, hash, etc) reference. And there are merits to using any of those things, depending upon the situation. Hashes are easy to work with and most similar to traditional objects.

 $object->{$attribute} = $value;

And whatnot. Arrays are much faster (typically 33% in tests I've done), but they suck to work with.

 $object->[15] = $value;        #the hell is '15'?

 (
  by the way, you can make this easier with variables defined to return the value, i.e.
  $object->[$attribute] = $value;       #assuming $attribute == 15
 )

Scalars are speciality and coderefs are left to the magicians. Don't get me wrong, coderefs as objects are nifty, but they can be tricky to work with.

So, I wanted a consistent interface. I'm not going to claim credit for this idea, since I think I originally read it in Object Oriented Programming in Perl (Damien's book). In fact, I think the error reporting method I use was also originally detailed in there. Anyway, I liked it a lot and decided I'd implement my own version of it. Besides, it's not like I'm the first guy to say that all attributes should be hidden behind mutators and accessors.

Basically, attributes are accessed and mutated via methods.

 $object->attribute($value);

For all attributes. This way, the internal object can be whatever you'd like. I used to use mainly arrays for the speed boost, but lately I use hashes a lot because of the ease of dumping and reading the structure for debugging purposes. But, with this consistent interface of using methods to wrapper the attributes, I can change the implementation of the object (scalar, array, hash, code, whatever) up in this module and *nothing* else needs to change.

Say you implemented a giant system in OO perl. And you chose hashrefs as your "object". But then you needed a big speed boost later, which you could easily get by going to arrays. You'd have to go through your code and change all instances of $object->{$attribute} to $object->[15] or whatever. That's an awful lot of work.

With everything wrappered up this way, changes can be made in the super object class and then automagically populate out everywhere with no code changes.

Enough with the philosophy, though. You need to know how this works.

It's easy enough:

 package Some::Class;

 Some::Class->add_attr('foo');

Now your Some::Class objects have a foo attribute, which can be accessed as above. If called with a value, it's the mutator which sets the attribute to the new value and returns the new value. If called without one, it's the accessor which returns the value.

 my $obj = Some::Class->new();
 $obj->foo('bar');
 print $obj->foo();                     #prints bar
 print $obj->foo('boo');        #prints boo
 print $obj->foo();                     #prints boo
 print $obj->foo('bang');       #prints bang
 print $obj->foo;                       #prings bang

add_attr calls should only be in your module. Never in your program. And they really should be defined up at the top.

Internally, an add_attr call creates a function inside your package of the name of the attribute which reflects through to the internal _isa_accessor method which handles the mutating and accessing.

You may alternatively pass in a list of attributes, if you don't want to do so much typing.

 __PACKAGE__->add_attr( qw( foo bar baz ) );

Gives you foo, bar, and baz attributes.

There is another syntax for add_attr, to define a different internal accessor:

 Some::Class->add_attr(['foo', 'accessor_creator']);

This creates method called 'foo' which talks to a separate accessor, in this case the closure returned by "accessor_creator" instead of a closure returned by _isa_accessor. This is useful if you want to create a validating method on your attribute.

Additionally, it creates a normal method going to _isa_accessor called '__b_foo', which is assumed to be the internal attribute slot your other accessor with use. In general, for a given "attribute", "__b_attribute" will be created for internal use. Also please note that you shouldn't ever create a method that starts with '__b_' (double underscore) since Basset reserves the right to automatically create methods named in that fashion. You've been warned.

"other_accessor" will get the object as the first arg (as always) and the name of the internal method as the second.

A sample accessor_creator could look like this:

 Some::Class->add_attr(['foo', 'accessor_creator']);

 sub accessor_creator {
        my $self = shift;
        my $attribute = shift;  #the external method name
        my $prop = shift;               #the internal "slot" that is a normal attribute

        #now we make our closure:
        return sub {
                my $self = shift;
                if (@_) {
                        my $val = shift;
                        if ($val == 7) {
                                return $self->$prop($val);
                        }
                        else {
                                return $self->error("Cannot store value...must be 7!", "not_7");
                        }
                }
                else {
                        return $self->$prop();
                }
        }
 }

And, finally, you can also pass in additional arguments as static args if desired.

 Some::Class->add_attr(['foo', 'accessor_creator'], 'bar');

 $obj->foo('bee');

 sub accessor_creator {
        my $self        = shift;
        my $method      = shift;
        my $static      = shift;        #'bar' in our example

        return sub {
                #do something with static argument
                .
                .
        }
 };

All easy enough. Refer to any subclasses of this class for further examples.

Basset::Object includes two other alternate accessors for you - regex and private.

 Some::Class->add_attr(['user_id', '_isa_regex_accessor', qr{^\d+$}, "Error - user_id must be a number", "NaN"]);

The arguments to it are, respectively, the name of the attribute, the internal accessor used, the regex used to validate, the error message to return, and the error code to return. If you try to mutate with a value that doesn't match the regex, it'll fail.

 Some::Class->add_attr(['secret', '_isa_private_accessor']);

private accessors add a slight degree of security. All they do is simply restrict access to the attribute unless you are within the class of the object. Note, that this causes access to automatically trickle down into subclasses.

add_class_attr

This is similar to add_attr, but instead of adding object attributes, it adds class attributes. You cannot have object and class attributes with the same name. This is by design. (error is a special case)

 Some::Class->add_attr('foo');                  #object attribute foo
 Some::Class->add_class_attr('bar'):    #class attribute bar

 print $obj->foo();
 print Some::Class->bar();

Behaves the same as an object method added with add_attr, mutating with a value, accessing without one. Note that add_class_attr does not have the capability for additional internal methods or static values. If you want those on a class method, you'll have to wrapper the class attribute yourself on a per case basis.

Note that you can access class attributes via an object (as expected), but it's frowned upon since it may be confusing.

class attributes are automatically initialized to any values in the conf file upon adding, if present.

add_trickle_class_attr

It's things like this why I really love Perl.

add_trickle_class_attr behaves the same as add_class_attr with the addition that it will trickle the attribute down into any class as it is called. This is useful for subclasses.

Watch:

 package SuperClass;

 SuperClass->add_class_attr('foo');
 SuperClass->foo('bar');

 package SubClass;
 @ISA = qw(SuperClass);

 print SubClass->foo();                 #prints bar
 print SuperClass->foo();               #prints bar

 print SuperClass->foo('baz');  #prints baz
 print SubClass->foo();                 #prints baz

 print SubClass->foo('dee');    #prints dee
 print SuperClass->foo();               #prints dee

See? The attribute is still stored in the super class, so changing it in a subclass changes it in the super class as well. Usually, this behavior is fine, but sometimes you don't want that to happen. That's where add_trickle_class_attr comes in. Its first call will snag the value from the SuperClass, but then it will have its own attribute that's separate.

Again, watch:

 package SuperClass;

 SuperClass->add_trickle_class_attr('foo');
 SuperClass->foo('bar');

 package SubClass;
 @ISA = qw(SuperClass);

 print SubClass->foo();                 #prints bar
 print SuperClass->foo();               #prints bar

 print SuperClass->foo('baz');  #prints baz
 print SubClass->foo();                 #prints bar

 print SubClass->foo('dee');    #prints dee (note we're setting the subclass here)
 print SuperClass->foo();               #prints baz

This is useful if you have an attribute that should be unique to a class and all subclasses. These are equivalent:

 package SuperClass;
 SuperClass->add_class_attr('foo');

 package SubClass
 SubClass->add_class_attr('foo');

 and

 package SuperClass;
 SuperClass->add_trickle_class_attr('foo');

You'll usually just use add_class_attr. Only use trickle_class_attr if you know you need to, since you rarely would. There is a *slight* bit of additional processing required for trickled accessors.

trickled class attributes are automatically initialized to any values in the conf file upon adding, if present.

References are a special case. If you add a hashref, that hashref will automatically be tied to a Basset::Container::Hash. Do not do this tying yourself, since bad things would occur. Once tied to Basset::Container::Hash, the hashref is now effectively layered so that subclasses may directly add to the hash without affecting parent values. Subclasses may not delete keys from the hash, only delete values they have added. Arrays are not tied.

Sometimes, you may be required to access the attribute via a wrapper method. For example:

 sub wrapper {
        my $self        = shift;

        my $existing = $self->trickled_ref();

        if (@_) {
                my $dumped = $self->dump($existing);    #take a dump of the ref
                no strict; no warnings;                                 #make sure nothing complains
                $self->trickled_ref(eval $dump);                #stick in a copy of it
        }

        return $self->trickled_ref(@_);
 }

Then you need to access the trickled method through the wrapper you've created. I don't want to add functionality like that into the add_trickle_class_attr method because I won't know when the value needs to be changed. You're getting back a reference, but then manipulating the value of the reference. So once you have a ref back, you immediately start changing the super class's value. The only way that I could fix it up here is to constantly re-copy the reference on every single access. But, of course, that then stops it from seeing changes in the super class, which is inconsistent.

Realistically, if you're using a ref and modifying it, you'll want wrapper methods to do things like add values within the ref, delete values within the ref, etc, you'll rarely (if ever) access the actual value of the ref directly. That is to say, you'll rarely change the hash pointed at, you'll change keys within the hash. So add_foo, delete_foo, change_foo, etc. wrappers that properly copy the hash as appropriate are the way to go. You can then still properly read the ref by just using the trickled attribute as always.

See the add_restrictions method below for an example of a wrapper like this.

add_default_class_attr

This adds a class attribute that is considered to be 'read-only' - it gets its value exclusively and utterly only from the conf file. Any modifications to this value are discarded in favor of the conf file value

attributes

Returns the attributes available to this object, based off of the flag passed in - "instance", "class", or "both". defaults to "instance".

Note - this method will not return attributes that begin with a leading underscore, as a courtesy.

is_attribute
add_wrapper

You can now wrapper methods with before and after hooks that will get executed before or after the method, as desired. Syntax is:

 $class->add_wrapper('(before|after)', 'method_name', 'wrapper_name');

That is, either before or after method_name is called, call wrapper_name first. Before wrappers are good to change the values going into a method, after wrappers are good to change the values coming back out.

For example,

 sub foo_wrapper {
        my $self = shift;
        my @args = @_; # (whatever was passed in to foo)
        print "I am executing foo!\n";
        return 1;
 }

 $class->add_wrapper('before', 'foo', 'foo_wrapper');

 Now, $class->foo() is functionally the same as:

 if ($class->foo_wrapper) {
        $class->foo();
 }

Ditto for the after wrapper.

 if ($class->foo) {
        $class->after_foo_wrapper;
 }

Wrappers are run in reverse add order. That is, wrappers added later are executed before wrappers added earlier. Wrappers are inherited in subclasses. Subclasses run all of their wrappers in reverse add order, then run all super class wrappers in reverse add order.

Wrapper functions should return a true value upon success, or set an error upon failure.

Performance hit is fairly negligible, since add_wrapper re-wires the symbol table. So be careful using this functionality with other methods that may re-wire the symbol table (such as Basset::Object::Persistent's _instantiating_accessor)

See also the extended syntax for add_attr, and Basset::Object::Persistent's import_from_db and export_to_db methods for different places to add in hooks, as well as the delegate attribute, below, for another way to extend code.

The performance hit for wrappers is reasonably small, but if a wrappered method is constantly being hit and the wrapping code isn't always used (for example, wrapping an attribute. If your wrapper only does anything upon mutation, it's wasteful, since the wrapper will still -always- be called), you can suffer badly. In those cases, an extended attribute or an explicit wrapper function of your own may be more useful. Please note that wrappers can only be defined on a per-method basis. If you want to re-use wrappers across multiple methods, you'll need your own wrapping mechanism. For example, using the extended attribute syntax to use a different accessor method.

There is an optional fourth argument - the conditional operator. This is a method (or coderef called as a method) that is executed before the wrapper is called. If the conditional returns true, the wrapper is then executed. If the conditional returns false, the wrapper is not executed.

 Some::Class->add_wrapper('after', 'name', 'validation', sub {
        my $self = shift;
        return @_;
 } );

That wrapper will only call the 'validation' method upon mutation (that is, when there are arguments passed) and not upon simple access.

Subclasses may define additional wrapper types.

Please don't wrapper attributes. Things may break if the attribute value is legitimately undef (normally an error condition). Instead, use the extended add_attr syntax to define a new accessor method for the attribute you wish to wrap. Or simply write your own subroutine and directly call a separately added attribute yourself.

error and errcode

error rocks. All error reporting is set and relayed through error. It's a standard accessor, and an *almost* standard mutator. The difference is that when used as a mutator, it returns undef instead of the value mutated to.

If a method fails, it is expected to return undef and set error.

example:

 sub someMethod {
        my $self = shift;
        my $value = shift;

        if ($value > 10){
                return 1;               #success
        }
        else {
                return $self->error("Values must be greater than 10");
        };
 };

 $object->someMethod(15) || die $object->error; #succeeds
 $object->someMethod(5)  || die $object->error; #dies with an error..."Values must be greater than 10"

Be warned if your method can return '0', this is a valid successful return and shouldn't give an error. But most of the time, you're fine with "true is success, false is failure"

As you can see in the example, we mutate the error attribute to the value passed, but it returns undef.

However, error messages can change and can be difficult to parse. So we also have an error code, accessed by errcode. This is expected to be consistent and machine parseable. It is mutated by the second argument to ->error

example:

 sub someMethod {
        my $self = shift;
        my $value = shift;

        if ($value > 10){
                return 1;               #success
        }
        else {
                return $self->error("Values must be greater than 10", "ERR77");
        };
 };

 $object->someMethod(15) || die $object->error;         #succeeds
 $object->someMethod(5)  || die $object->errcode;       #dies with an error code ... "ERR77"

If your code is looking for an error, read the errcode. if a human is looking at it, display the error. Easy as pie.

Both classes and objects have error methods.

 my $obj = Some::Class->new() || die Some::Class->error();
 $obj->foo() || die $obj->error();

Note that error is a special method, and not just a normal accessor or class attribute. As such:

 my $obj = Some::Class->new();
 Some::Class->error('foo');
 print $obj->error();                   #prints undef
 print Some::Class->error();    #prints foo

i.e., you will not get a class error message by calling ->error on an object.

error also posts an 'error' notification to the notification center. See Basset::NotificationCenter for more information. The notification will not be posted if the optional third "silently" parameter is passed.

 Some::Class->error('foo', 'foo_code', 'silently'); 

->error can (and will) die if an error occurs very very early in the compilation process, namely if an error occurs before the 'exceptions' attribute is defined. It is assumed that if an error occurs that early on, it's a very bad thing, and you should bail out.

You may also always cause an exception by passing in the double plus secret fourth parameter - "throw anyway".

 Some::Class->error('foo', 'foo_code', 0, 'HOLY COW BAIL OUT NOW!');

Use the throw anyway parameter with care. It should be reserved to cover coding errors. An issue that if it occurs, there is no way to continue and the programmer needs to fix it in advance. For example, _accessor throws an exception if you try to call it as a class method, and with good reason.

rawerror

If you're using a formatted error string, ->error will always return the formatted value to you. ->rawerror will return the formattable data.

 $obj->error('foo');
 print $obj->error(); #prints 'foo'
 print $obj->rawerror(); #prints 'foo'

 $obj->error(['foo %d', 77]);
 print $obj->error(); #prints 'foo 77'
 print $obj->rawerror(); #prints ARRAY0x1341 (etc.)
errcode

errcode is an accessor ONLY. You can only mutate the errcode via error, see above.

 print $obj->errcode;

Both objects and classes have errcode methods.

 my $obj = Some::Class->new() || die Some::Class->errcode();
 $obj->foo() || die $obj->errcode

Do not ever ever ever define an error code that starts with "B". Those are reserved for framework error codes. Otherwise, standard C-style "namespace" conventions apply - give it a reasonably unique prefix. Preferrably one that helps people identify where the error was. I like to use the the initials of the module name.

 package Basset::Object::Persistent;  #returns BOP-## error codes.
errstring

errstring is a convenience accessor, it returns the error and code concatenated.

$obj->someMethod() || die $obj->errstring; #dies "Values must be greater than 10...with code(ERR77)"

errvals

similar to errstring, but returns the error and errcode in an array. This is great for bubbling up error messages. Note that errvals will also include the extra 'silently' parameter to prevent bubbled errors from posting notifications.

 $attribute = $obj->foo() or return $self->error($obj->errvals);
usererror

errors are great, but they can be a bit cryptic. usererror takes the last error message and re-formats it into a more end user friendly syntax. If there's no way to re-format it, it just returns the actual error.

Alternatively, you can also use the error translator to change an error code into something more user friendly

See "errortranslator", below, for more info.

wipe_errors

Wipes out the current error message and error code.

notify

Used for non-fatal messages, usually an error message that shouldn't cause things to abort. Expects at least one argument, the notification being posted. Additional arguments will be passed through to any handlers.

 sub lockThing {
        my $self = shift;
        my $thing = shift;

        if ($thing->locked) {
                $self->notify("info", "Cannot lock - thing is already locked");
        } else {
                $thing->lock();
        };

        return 1;
 }

In this example, we have a method called "lockThing" that locks a thing (whatever that means). But it only locks the thing if it is not already locked. If it is locked, it sends an informational message that the thing is already locked. But that's not fatal - we still end up with a locked thing, so we're happy no matter what. No need to kick back an error.

notify is a wrapper around the notification center.

 $obj->notify('foo') == Basset::NotificationCenter->postNotification('object' => $obj, 'notification' => 'foo');
add_restrictions

Class method. Expects a hash of arrayrefs, listing permissions and method re-maps.

 Some::Package->add_restrictions(
        'readonly' => [
                'commit'        => 'failed_restricted_method',
                'write'         => 'failed_restricted_method',
        ],
        'writeonly' => [
                'load'          => 'failed_restricted_method',
        ],
        'subuser'       => [
                'commit'        => 'validating_commit'
        ]
 );

We require a hash of arrayrefs so that we can guarantee the order in which the methods will be re-mapped.

This specifies that Some::Package can be restricted in several ways, with a 'readonly' restriction, a 'writeonly' restriction, and a 'subuser' restriction. If the package is restricted, then the methods are re-mapped as defined. i.e., if the 'readonly' restriction is in place, then calling 'commit' actually calls "failed_restricted_method" Add restrictions by calling either add_restricted_method or (better!) by calling restrict.

 my $inline_class = Some::Package->restrict('readonly');

 my $o = Some::Package->new();
 $o->commit() || die $o->errstring; #succeeds!

 my $o2 = $inline_class->new();
 $o2->commit() || die $o2->errstring; #fails. access to commit is restricted.

see add_restricted_method and restrict, below.

add_restricted_method

Given a restriction and a method, restricts only that method to that restriction.

 Some::Package->add_restricted_method('writeonly', 'commit');

This applies the writeonly restriction to the commit method (as defined above in the add_restrictions pod). Note that this does not apply the restriction to the 'write' method, only to 'commit'.

You will rarely (if ever) use this method, use 'restrict' instead.

failed_restricted_method

Simple convenience method. Always fails with a known error and errorcode - "Access to this method is restricted", "BO-16"

inline_class

Another internal method that you will rarely, if ever call.

 my $inline_class = Some::Package->inline_class();

This creates a new class, which is a subclass of Some::Package. The only difference is that it has its restricted flag turned on. To apply restrictions, use the restrict method instead.

restrict

Called on a class, this creates a new subclass with restrictions in place.

 my $inline_class = Some::Package->restrict('readonly', 'writeonly', 'subuser');

Will return a new class which is a subclass of Some::Package that has the readonly, writeonly, and subuser restrictions applied. Note that restrictions are applied in order, so that a later one may wipe out an earlier one. In this case, the re-defined commit method from subuser wins over the one defined in writeonly.

This is used to restrict access to class methods, probably depending upon some sort of user permission scheme.

nonrestricted_parent

Called on a class, returns the first non-restricted parent of that class

dump

->dump dumps out the object (using Data::Dumper internally), this is useful to show you what an object looks like.

 print $obj->dump

Alternatively, you can hand in something to dump.

 print $obj->dump($something_else);
new

Finally! The constructor. It's very easy, for a minimalist object, do this:

 my $obj = Class->new() || die Class->error();

Ta da! You have an object. Any attributes specified in the conf file will be loaded into your object. So if your conf file defines 'foo' as 'bar', then $obj->foo will now equal 'bar'.

If you'd like, you can also pass in method/value pairs to the constructor.

 my $obj = Class->new(
        'attribute' => '17',
        'foo'           => 'baz',
        'method'        => '88'
 ) || die Class->error();

This is (roughly) the same as:

 my $obj = Class->new() || die Class->error();

 $obj->attribute(17) || die $obj->error();
 $obj->foo('baz') || die $obj->error();
 $obj->method(88) || die $obj->error();

Any accessors or methods you'd like may be passed to the constructor. Any unknown pairs will be silently ignored. If you pass a method/value pair to the constructor, it will override any equivalent method/value pair in the conf file.

Also note that any methods that return undef are assumed to be errors and will cause your construction to fail. But, if you explicitly pass in an 'undef' parameter and your method/mutator fails, then we will assume you know what you're doing and it's allowed. You only fail if you pass in a value other than undef, but the result of the method call is an undef.

 $obj = Class->new(
        'attr' => undef
 ) || die Class->error;

If you really really need to to explicitly set something to undef, you'll need to do it afterwards:

 $obj = Class->new();
 $obj->method(undef);

Note that in this case, setting 'method' to undef isn't actually an error, since that's what you want to do. But, the constructor has no way to know when an accessor returning undef is an error, or when you explicitly set the accessor to undef.

init

The object initializer. Arguably more important than the constructor, but not something you need to worry about. The constructor calls it internally, and you really shouldn't touch it or override it. But I wanted it here so you know what it does.

Simply, it iterates through the conf file and mutates any of your object attributes to the value specified in the conf file. It then iterates through the hash you passed to ->new() and does the same thing, overriding any conf values, if necessary.

init is smart enough to use all super class values defined in the conf file, in hierarchy order. So if your conf file contains:

 define package SuperClass

 foo = 'bar'

And you're creating a new SubClass object, then it will get the default of foo = 'bar' as in the conf file, despite the fact that it was not defined for your own package. Naturally, the more significant definition is used.

 define package SuperClass

 foo = 'bar'

 define package SubClass

 foo = 'baz'

SuperClass objects will default foo to 'bar', SubClass objects will default foo to 'baz'

If the initializer is given a hashref as its first argument, then it will use those values first. Note that values passed in via a hashref like this may be overridden by defaults AND by passed in arguments.

For example:

 #in your conf file
 define package Some::Class
 foo = bar
 one = two
 alpha = beta

 #in your code

 my $x = Some::Class->new(
        {
                'foo' => 'fnar',
                'mister' => 'peepers',
                'alpha' => 'kappa',
        },
        'alpha' => 'gamma'
 );

 print $x->foo; #prints 'bar' (from conf file)
 print $x->one; #prints 'two' (from conf file)
 print $x->mister; #prints 'peepers' (from initial hash)
 print $x->alpha; #prints 'gamma' (passed argument)
pkg

Returns the package (class) of the object. Note that this is not necessarily the same as ref $object. This is because of some wackiness in how perl handles some internal things that I don't quite understand. Suffice to say that even if you bless an object into a class Foo, ref $object may not always be 'Foo'. Sometimes it may be 'main::Foo' and sometimes it may be '::Foo'. I'll leave the reasons why for others to document. This method is just here to keep that from biting you.

factory

Abstract factory constructor. Works just like ->new() except it expects to receive a type. The types are listed in the conf file to determine which type of object to instantiate.

In conf file:

 define package Basset::Object
 types  @= user=Basset::User
 types  @= group=Basset::Group

And then, in your program:

 my $user = Basset::Object->factory(
        'type' => 'user'
 );

 $user is a Basset::User object. Use for objects that are supposed to be used in multiple applications. This allows you to swap
 out particular objects for different (but similar!) ones by just changing the conf file, not all your code.
copy

Copies the object. Be warned! Copy does a deep copy of the object. So any objects/references/etc pointed to by the original object will also be copied.

You may optionally pass in a different object/structure and copy that instead.

 my $backupBoard = $game->copy($game->board);
pkg_for_type

Use internally by factory(), also sometimes useful in code. Given a type, returns the class as defined in the conf file.

 my $class = Basset::Object->pkg_for_type('user'); #returns Basset::User (for example)
inherits

This method is deprecated and b<will> be removed in Basset 1.0.4. The concept remains the same, but I, like an idiot, overlooked a much simpler syntax. Just push the result of pkg_for_type onto @ISA as normal.

use Basset::Object; our @ISA = Basset::Object->pkg_for_type('object');

Voila! Same effect. You may now proceed to read the long expository explanation here as to why you would do that. This exposition is going to slide over into the pkg_for_type method.

Basset is a nice framework. It kicks all sorts of ass. But, it's entirely possible that it's not quite functional enough for you. Let's say you work for some company, WidgetTech.

WidgetTech has information in a database, it's mostly fairly object-relational in nature, you can certainly use Basset::Object::Persistent. So you go through and write up 50 modules that all inherit from Basset::Object::Persistent. All is right with the world.

3 months later, someone decides that instead of deleting old records from the database, as you'd been doing, you need to instead leave them there and change their status flag to 'D'. The status flag is already there (you use it for other things, active, pending suspended, etc.). So you don't need to change anything in your modules - just add the drop down to your interface and all is good.

2 days later, you're getting angry phonecalls from users saying that deleted data is showing up in the system. This is bad. You forgot that Basset::Object::Persistent doesn't know anything about status flags and just loads up everything. Very bad.

Options? Well, you could go into every single module (50 of 'em) and override their load_all and delete methods. But man, that's gonna take forever. And probably get out of sync. And be a maintenance disaster. And it's just not the Basset way.

So what do you do instead? You hack up Basset::Object::Persistent. You modify the load_all method so that it tacks on a where clause to exclude status of 'D'. You modify delete so that it just changes the status and re-commits. All is right with the world.

A month later, I release a new version of Basset, you forget about the modifications, upgrade, and start getting calls from angry users. You need to re-hack the system.

So, you realize, this isn't the best way to go. Instead, you write a new object - WidgetTech::Object::Persistent. WidgetTech::Object::Persistent inherits from Basset::Object::Persistent. You then do a search and replace on your 50 modules to change occurances of Basset::Object::Persistent to WidgetTech::Object::Persistent. You put your modified load_all and delete methods in WidgetTech::Object::Persistent and all is right with the world. I release a new version of Basset a week later, you drop it into place, there are no issues.

Two months later, you decide that you need to override a method in Basset::Object. Or, you want a new method accessible to all of your objects. Easy - put it in the root class. Now, you've learned enough not to hack up Basset::Object, so you create WidgetTech::Object and add in your new method to there. Anything that did inherit from Basset::Object should now inherit WidgetTech::Object and everything's fine.

Whoops. Except for WidgetTech::Object::Persistent. You have an inheritance tree like this:

 Basset::Object
 ^         ^
 |         |
 |       WidgetTech::Object
 |
 Basset::Object::Persistent
 ^
 |
 WidgetTech::Object::Persistent

But you need this:

 Basset::Object
 ^
 |
 WidgetTech::Object
 ^
 |
 Basset::Object::Persistent
 ^
 |
 WidgetTech::Object::Persistent

Your W::O::P inherit B::O::P which inherits B::O. And this all bypasses WidgetTech::Object. You don't want to stick the methods into WidgetTech::Object::Persistent, since they need to be accessible to all classes, not just persistent ones. You (obviously) know better than to hack Basset::Object::Persistent to inherit from WidgetTech::Object instead of Basset::Object. So what do you do?

And all of this long expository setup brings us to the inherits method. Inheritance in Basset does not usually directly use @ISA. Instead, it uses the inherits class method and a classtype.

 package Basset::Object::Persistent;

 use Basset::Object;
 #deprecated old way:
 #Basset::Object->inherits(__PACKAGE__, 'object');
 #fancy new way:
 @ISA = ( Basset::Object->pkg_for_type('object') );

Voila! That's basically equivalent to:

 package Basset::Object::Persistent;

 use Basset::Object;
 @ISA = qw(Basset::Object);

Now, everybody knows that familiar @ISA = ... syntax, so why change it? If you read that story up above, you already know. This moves inheritance out of the module tree and into your conf file. So now if you want to use WidgetTech::Objects as your root object, you just change your conf file:

 types %= object=WidgetTech::Object

And blam-o. You have a new root class. Now, of course, Basset::Object will always be the top level root object in a Basset system. But you can now pretend that you have a different object instead. This new object sits in between Basset::Object and the rest of the world. Anything you want to change in Basset::Object is fair game. The only thing that must always be in Basset::Object is the inherits method. Other modules will expect Basset::Object to call inherits at their start to set up their @ISA for them, so you can't do away with it entirely.

inherits will die if it fails. It's a compilation error, so it's not going to let you off the hook if it can't set up a relationship.

You'll mostly be fine with using @ISA in your code.

 package WidgetTech::Widget;
 @ISA = qw(WidgetTech::Object::Persistent);

You have control over WidgetTech::Widget and WidgetTech::Object::Persistent, and it's highly unlikely that you'll need to change your inheritance tree. Modifications can go in your super class or your subclass as needed and nobody cares about re-wiring it.

isa_path

This is mainly used by the conf reader, but I wanted to make it publicly accessible. Given a class, it will return an arrayref containing all of the superclasses of that class, in inheritence order.

Note that once a path is looked up for a class, it is cached. So if you dynamically change @ISA, it won't be reflected in the return of isa_path. Obviously, dynamically changing @ISA is frowned upon as a result.

module_for_class

Used mainly internally. Converts a perl package name to its file system equivalent. So, Basset::Object -> Basset/Object.pm and so on.

conf

conf is just a convenience wrapper around read_conf_file.

 $obj->conf === Basset::Object::Conf->read_conf_file;
today

Convenience method. Returns today's date in a YYYY-MM-DD formatted string

now

Convenience method. Returns a timestamp in a YYYY-MM-DD HH:MM:SS formatted string

gen_handle

returns a filehandle in a different package. Useful for when you need to open filehandles and pass 'em around.

 my $handle = Basset::Object->gen_handle();
 open ($handle, "/path/to/my/list");

All but identical to gensym in Symbol by this point.

perform

if I were writing this in objective-C, I'd call it performSelectors:withObjects: Ho hum. I've really grown fond of the objective-C syntax. Anyway, since I can't do that, it's just called perform.

 $object->perform(
        'methods' => [qw(name password address)],
        'values' => ['Jim', 'password', 'Chew St']
 ) || die $object->errstring;

Given a list of methods and values, it calls each method in turn with each value passed. If anything fails, it an error and stops proceeding through the list.

Optionally, you may pass in a dereference hash to dereference an arrayref or hashref.

 $object->perform(
        'methods' => [qw(name password address permission)],
        'values' => ['Jim', 'password', 'Chew St', ['PT07', 'AB']],
        'dereference' => [qw(permission)],
 ) || die $object->errstring;

With the dereference value, it calls

 $object->permission('PT07', 'AB');

Without the dereference value, it calls

 $object->permission(['PT07', 'AB']);

This can (obviously) even be called with a single method. This is preferrable to just calling $obj->$method(@args) in the code if $method is not guaranteed to be callable since perform automatically does a 'can' check on the method for you.

Optionally, you may also pass in a continue parameter.

 $object->perform(
        'methods'               => [qw(name password address permission)],
        'values'                => ['Jim', 'password', 'Chew St', ['PT07', 'AB']],
        'dereference'   => [qw(permission)],
        'continue'              => 1
 ) || die $object->errstring;

continue should be used with great caution. continue will cause execution to continue even if an error occurs. At the end, you'll still get an undef back, and your error message will be a list of \n delimited error messages, your error code will be a list of \n delimited error codes. This is appropriate if you want to set multiple attributes at once (or other methods that are indpendent of each other) and want to report all errors en masse at the end.

stack_trace

A method useful for debugging. When called, returns a stack trace.

sub some_method { my $self = shift; #you know something weird happens here. print STDERR $self->stack_trace(); };

no_op

no_op is a simple little method that just always returns 1, no matter what. Useful for cases where you want to be able to call a method and have it succeed, such as a generic place holder.

system_prefix

Returns the prefix used by the system for internal methods as generated by add_attr and the like.

privatize

Returns a method prepended with the system prefix, useful for making private methods.

 Some::Class->privatize('foo'); #returns Some::Class->system_prefix . 'foo';
deprivatize

Returns a method with the system prefix removed, useful for unmaking private methods.

 Some::Class->deprivatize('__b_foo'); #returns 'foo';
is_private

Returns a true value if the method is private (starts with system prefix), and false otherwise.

 Some::Class->is_private('__b_foo');    #returns true;
 Some::Class->is_private('foo');                #returns false;
cast

Returns the object casted to the given class.

 my $object = Some::Class->new();
 my $casted = $object->cast('Some::Class::Subclass');

If passed a second true argument, returns a copy of the object casted.

 my $object = Some::Class->new();
 my $castedCopy = $object->cast('Some::Class::Subclass', 'copy');

ATTRIBUTES ^

errortranslator

The errortranslator needs to be set to a hashref, and it translates programmer readable errors into user readable errors. It's clunky and a mess and a hack, but it works.

 __PACKAGE__->errortranslator(
        {
                'violation of key constraint foo: Cannot INSERT' => 'Please specify a value for foo'
        }
 );

 $obj->do_something || die $obj->error();       # dies 'violation of key constraint foo: Cannot INSERT'
 $obj->do_something || die $obj->usererror();# dies 'Please specify a value for foo'

The error translator looks at the error values, and if a more friendly user error exists, it returns that one instead. errortranslator looks at and returns (in order):

 the actual error,
 the raw error, 
 the error code, 
 a '*' wildcard, 
 and then just returns the original error w/o modification.

Be careful using the '*' wildcard. This will translate -any- error message that doesn't have a friendlier version.

use_real_errors

use_real_errors bypasses the errortranslator and only returns the errstring. This is useful so that your developers can get back useful information, but your users can get back a friendly message.

delegate

This is borrows from objective-C, because I like it so much. Basically, the delegate is a simple catch all place for an additional object that operates on your current object.

 sub some_method {
         my $self = shift;
         #call the delegate when we call some_method
         if ($self->delegate && $self->delegate->can('foo')) {
                $self->delegate->foo(@useful_arguments);
         };
 }
types

Defined in your conf file. Lists types used by the factory and pkg_for_type. See those methods for more info. Use a hashref in the conf file:

 types %= user=Basset::User
 types %= group=Basset::Group
 #etc

That is, types should be an array of values that are = delimited. type=class.

restrictions

This stores the restrictions that could be added to this class, but not necessarily the ones that are in effect. Add new restrictions with the add_restriction method.

restricted

Boolean flag. returns 0 if the class is non-restricted, or 1 if it is restricted.

exceptions

boolean flag 1/0. Off by default. Some people, for some silly reason, like to use exceptions. Personally, I avoid them like the plague. Nonetheless, I'm an agreeable sort and wanted to provide the option. Standard procedure is to call a method or bubble up an error:

 sub method {
        my $self = shift;

        my $obj = shift;

        $obj->trysomething() or return $self->error($obj->errvals);
 }

methods return undef, so if the return is undefined, you bubble it back up until something can handle it. With exceptions enabled, the error method (called somewhere inside $obj's trysomething method) would instead die with an error of the errorcode passed. Additionally, the error itself is set in the last_exception attribute. So you write your method call this way, if exceptions are enabled:

 sub method {
        my $self = shift;
        my $obj = shift;

        eval {
                $obj->trysomething();
        }
        if ($@ =~ /interesting error code/) {
                print "We died because of " . $obj->last_exception . "\n";
        } else {
                $obj->error($obj->errvals);#re-throw the exception
        }
 }

Note that last_exception should be used to find out the error involved, not the ->error method. This is because you can't know which object actually threw the exception.

last_exception

stores the message associated with the last exception

SEE ALSO ^

Basset::Object::Conf, Basset::Object::Persistent

COPYRIGHT (again) and license ^

Copyright and (c) 1999, 2000, 2002, 2003, 2004, 2005 James A Thomason III (jim@jimandkoka.com). All rights reserved.

Basset is distributed under the terms of the Artistic License.

CONTACT INFO ^

So you don't have to scroll all the way back to the top, I'm Jim Thomason (jim@jimandkoka.com) and feedback is appreciated. Bug reports/suggestions/questions/etc. Hell, drop me a line to let me know that you're using the module and that it's made your life easier. :-)

syntax highlighting: