NAME

Class::Tangram - create constructors, accessor and update methods for objects from a Tangram-compatible object specification.


SYNOPSIS

 # simple package - four attributes "foo", "bar" (integers),
 # "baz" and "quux" (strings)
 package MyObject;
 use base qw(Class::Tangram);
 our $schema = {
    fields => { int => [ qw(foo bar) ],
                string => [ qw(baz quux) ] }
    };
 package main;
 my $object = MyObject->new(foo => 2, baz => "hello");
 print $object->foo();
 $object->set_quux("Something");
 # More detailed example, displaying many of the features of
 # Class::Tangram
 package Orange;
 use base qw(Class::Tangram);
 use vars qw($schema);
 use Tangram::Ref;
 # define the schema (ie, allowed attributes) of this
 # object.  See the Tangram::Schema man page for an
 # introduction to the Tangram schema syntax.
 $schema = {
     table => "oranges",
     fields => {
         int => {
             juiciness => undef,
             segments => {
                 # this code reference is called when this
                 # attribute is set, to check the value is
                 # OK
                 check_func => sub {
                     die "too many segments"
                         if (${ shift } > 30);
                 },
                 # the default for this attribute.
                 init_default => 7,
             },
         },
         ref => {
             grower => undef,
         },
         # 'required' attributes - insist that these fields are set,
         #    both with constructor and set()/set_X methods
         string => {
             # true: 'type' must have non-empty value
             type => { required => 1 },
             # false: 'tag' must be defined but may be empty
             tag => { required => '' },
         },
         # fields allowed by Class::Tangram but not ever
         # stored - no type checking without check_func
         transient => [ qw(_tangible) ],
     },
 };
 Class::Tangram::import_schema("Orange");
 package Project;
 # here's where we build the individual object schemas into
 # a Tangram::Schema object, which the Tangram::Storage
 # class uses to know which tables and columns to find
 # objects.
 use Tangram::Schema;
 my $dbschema = Tangram::Schema->new
     ({ classes => [ 'Orange' => $Orange::schema,
                     'MyObject' => $MyObject::schema ]});
 sub schema { $dbschema };
 package main;
 # See Tangram::Relational for instructions on using
 # "deploy" to create the database this connects to.  You
 # only have to do this if you want to write the objects to
 # a database.
 use Tangram::Relational;
 my ($dsn, $u, $p);
 my $storage = Tangram::Relational->connect
                   (Project->schema, $dsn, $u, $p);
 # This is how you create instances
 my $orange = Orange->new(
                          juiciness => 8,
                          type => 'Florida',
                          tag => '',  # required
                         );
 # Store them
 $storage->insert($orange);
 # This is how you get values out of the objects
 my $juiciness = $orange->juiciness;
 # a "ref" must be set to a blessed object, any object
 my $grower = bless { name => "Joe" }, "Farmer";
 $orange->set_grower ($grower);
 # these are all illegal - type checking is fairly strict
 my $orange = eval { Orange->new; };         print $@;
 eval { $orange->set_juiciness ("Yum"); };   print $@;
 eval { $orange->set_segments (31); };       print $@;
 eval { $orange->set_grower ("Mr. Nice"); }; print $@;
 eval { $orange->set_type (''); };           print $@;
 eval { $orange->set_type (undef); };        print $@;
 eval { $orange->set_tag (undef); };         print $@;
 # if you prefer
 $orange->get( "juiciness" );
 $orange->set( juiciness => 123 );
 # Re-configure init_default
 $orange->set_init_default( juiciness => sub { int(rand(45)) } );


DESCRIPTION

Class::Tangram is a common base class originally intended for use with Tangram objects, that gives you free constructors, access methods, update methods, and a destructor that should help in breaking circular references for you. Type checking is achieved by parsing the Tangram schema for the object, which is contained within the object class in an exported variable $schema.

After writing this I found that it was useful for merely adding type checking and validation to arbitrary objects. There are several modules on CPAN to do that already, but many don't have finely grained type checking, and none of them integrated with Tangram or any other object persistence framework quite so easily.


DEPENDENCIES

The following modules are required to be installed to use Class::Tangram:

   Set::Object => 1.02
   Pod::Constants => 0.11
   Test::Simple => 0.18
   Date::Manip => 5.21

Test::Simple and Date::Manip are only required to run the test suite.

If you find Class::Tangram passes the test suite with earlier versions of the above modules, please send me an e-mail.

MODULE RELEASE

This is Class::Tangram version 1.10.


METHODS

Constructor

Class->new (attribute1 => value, attribute2 => value)
sets up a new object of type Class, with attributes set to the values supplied.

Can also be used as an object method, in which case it returns a copy of the object, without any deep copying.

Accessing & Setting Attributes

$instance->set(attribute => $value, ...)
Sets the attributes of the given instance to the given values. croaks if there is a problem with the values.

$instance->get($attribute)
Gets the value of $attribute. If the attribute in question is a set, and this method is called in list context, then it returns the MEMBERS of the set (if called in scalar context, it returns the Set::Object container).

$instance->attribute($value)
If $value is not given, then this is equivalent to $instance->get("attribute")

If $value is given, then this is equivalent to $instance->set("attribute", $value). This usage issues a warning; you should change your code to use the set_attribute syntax for better readability (object methods should always be a verb).

$instance->get_attribute
$instance->set_attribute($value)
Equivalent to $instance->get("attribute") and $instance->set(attribute => $value), respectively.

$instance->attribute_includes(@objects)
$instance->attribute_insert(@objects)
$instance->attribute_size
$instance->attribute_clear
$instance->attribute_remove(@objects)
Equivalent to calling $instance->attribute->includes(@objects), etc. This only works if the attribute in question is a ``set'' or ``iset'' type.

$instance->getset($attribute, $value)
If you're replacing the AUTOLOAD function in your Class::Tangram derived class, but would still like to use the behaviour for one or two fields, then you can define functions for them to fall through to the Class::Tangram method, like so:
 sub attribute { $_[0]->SUPER::getset("attribute", $_[1]) }

Attribute Type Checking

Class::Tangram provides type checking of attributes, which are defined via three per-attribute options:

check_func
A function that is called with a reference to the new value in $_[0]. It should call die() if the value is bad.

destroy_func
If anything special needs to happen to this attribute before the object is destroyed (or when someone calls $object->clear_refs()), then define this.

required
If this option is set to a true value, then the attribute must be set to a true value to pass type checking. If it is defined but logically false, (ie ``'' or 0), then the attribute must also be defined, but may be logically false. This is implemented by a simple wrapper to the check_func for the attribute.

These functions are automatically defined for the built-in Tangram types. In some cases, extra information about what may be put into the attribute is cleaned from the SQL column type definition. This is covered by the section on parse_X functions, below.

Default Type Checking

 # The following list is eval'ed from this documentation, and used as
 # default attribute options for the specified types.  So, eg, the
 # default "init_default" for "set" types is a subroutine that returns
 # a new Set::Object container.
 # "parse" is special - it is passed the options hash given by the
 # user and should return (\&check_func, \&destroy_func).  This is how
 # the magical string type checking is performed - see the entry for
 # parse_string(), below.
 int         => { check_func   => \&check_int },
 real        => { check_func   => \&check_real },
 string      => { parse        => \&parse_string },
 ref         => { check_func   => \&check_obj,
                  destroy_func => \&destroy_ref },
 array       => { check_func   => \&check_array,
                  destroy_func => \&destroy_array },
 iarray      => { check_func   => \&check_array,
                  destroy_func => \&destroy_array },
 flat_array  => { check_func   => \&check_flat_array },
 set         => { check_func   => \&check_set,
                  destroy_func => \&destroy_set,
                  init_default => sub { Set::Object->new() }, },
 iset        => { check_func   => \&check_set,
                  destroy_func => \&destroy_set,
                  default      => sub { Set::Object->new() }, },
 dmdatetime  => { check_func   => \&check_dmdatetime },
 rawdatetime => { check_func   => \&check_rawdatetime },
 rawdate     => { check_func   => \&check_rawdate },
 rawtime     => { check_func   => \&check_rawtime },
 flat_hash   => { check_func   => \&check_flat_hash },
 transient   => { check_func   => \&check_nothing },
 hash        => { check_func   => \&check_hash,
                  destroy_func => \&destroy_hash },
 perl_dump   => { check_func   => \&check_nothing }
check_X (\$value)
This series of internal functions are built-in check_func functions defined for all of the standard Tangram attribute types.
check_string
checks that the supplied value is less than 255 characters long.

check_int
checks that the value is a (possibly signed) integer

check_real
checks that the value is a real number, by stringifying it and matching it against (m/^-?\d*(\.\d*)?(e-?\d*)?$/). Inefficient? Yes. Patches welcome.

check_obj
checks that the supplied variable is a reference to a blessed object

check_flat_array
checks that $value is a ref ARRAY and that all elements are unblessed scalars. Does NOT currently check that all values are of the correct type (int vs real vs string, etc)

check_array
checks that $value is a ref ARRAY, and that each element in the array is a reference to a blessed object.

check_set
checks that $value->isa(``Set::Object'')

check_rawdate
checks that $value is of the form YYYY-MM-DD, or YYYYMMDD, or YYMMDD.

check_rawtime
checks that $value is of the form HH:MM(:SS)?

check_rawdatetime
checks that $value is of the form YYYY-MM-DD HH:MM(:SS)? (the time and/or the date can be missing), or a string of numbers between 6 and 14 numbers long.

check_dmdatetime
checks that $value is of the form YYYYMMDDHH:MM:SS, or those allowed for rawdatetime.

check_flat_hash
checks that $value is a ref HASH and all values are scalars. Does NOT currently check that all values are of the correct type (int vs real vs string, etc)

check_hash
checks that $value is a ref HASH, that every key in the hash is a scalar, and that every value is a blessed object.

check_nothing
checks whether Australians like sport

destroy_X ($instance, $attr)
Similar story with the check_X series of functions, these are called during object destruction on every attribute that has a reference that might need breaking. Note: these functions all assume that attributes belonging to an object that is being destroyed may be destroyed also. In other words, do not allow distinct objects to share Set::Object containers or hash references in their attributes, otherwise when one gets destroyed the others will lose their data.

Available functions:

destroy_array
empties an array

destroy_set
Calls Set::Object::clear to clear the set

destroy_hash
empties a hash

destroy_ref
destroys a reference.

parse_X ($attribute, { schema option })
Parses the schema option field, and returns one or two closures that act as a check_X and a destroy_X function for the attribute.

This is currently a very ugly hack, parsing the SQL type definition of an object. But it was bloody handy in my case for hacking this in quickly. This is probably unmanagably unportable across databases; but send me bug reports on it anyway, and I'll try and make the parsers work for as many databases as possible.

This perhaps should be replaced by primitives that go the other way, building the SQL type definition from a more abstract definition of the type.

Available functions:

parse_string
parses SQL types of:
CHAR(N), VARCHAR(N)
closure checks length of string is less than N characters

TINYBLOB, BLOB, LONGBLOB
checks max. length of string to be 255, 65535 or 16777215 chars respectively. Also works with ``TEXT'' instead of ``BLOB''

SET(``members'', ``of'', ``set'')
checks that the value passed is valid as a SQL set type, and that all of the passed values are allowed to be a member of that set.

ENUM(``possible'', ``values'')
checks that the value passed is one of the allowed values.

Quick Object Dumping and Destruction

$instance->quickdump
Quickly show the blessed hash of an object, without descending into it. Primarily useful when you have a large interconnected graph of objects so don't want to use the x command within the debugger. It also doesn't have the side effect of auto-vivifying members.

This function returns a string, suitable for print()ing. It does not currently escape unprintable characters.

$instance->DESTROY
This function ensures that all of your attributes have their destructors called. It calls the destroy_X function for attributes that have it defined, if that attribute exists in the instance that we are destroying. It calls the destroy_X functions as destroy_X($self, $k)

$instance->clear_refs
This clears all references from this object, ie exactly what DESTROY normally does, but calling an object's destructor method directly is bad form. Also, this function has no qualms with loading the class' schema with import_schema() as needed.

This is useful for breaking circular references, if you know you are no longer going to be using an object then you can call this method, which in many cases will end up cleaning up most of the objects you want to get rid of.

However, it still won't do anything about Tangram's internal reference to the object, which must still be explicitly unlinked with the Tangram::Storage->unload method.


FUNCTIONS

The following functions are not intended to be called as object methods.

Schema Import

Class::Tangram::import_schema($class)
Parses a tangram object schema, in ${"${class}::schema"} to the internal representation used to check types values by set(). Called automatically on the first get(), set(), or new() for an object of a given class.

Run-time type information

It is possible to access the data structures that Class::Tangram uses internally to verify attributes, create objects and so on.

Class::Tangram keeps seven internal hashes:

%types
$types{$class}->{$attribute} is the tangram type of each attribute, ie ``ref'', ``iset'', etc. See the Tangram::Type manpage.

%attribute_options
$attribute_options{$class}->{$attribute} is the options hash for a given attribute.

%required_attributes
$required_attributes{$class}->{$attribute} is the 'required' option setting for a given attribute.

%check
$check{$class}->{$attribute} is a function that will be passed a reference to the value to be checked and either throw an exception (die) or return true.

%cleaners
$attribute_options{$class}->{$attribute} is a reference to a destructor function for that attribute. It is called as an object method on the object being destroyed, and should ensure that any circular references that this object is involved in get cleared.

%abstract
$abstract->{$class} is set if the class is abstract

%init_defaults
$init_defaults{$class}->{$attribute} represents what an attribute is set to automatically if it is not specified when an object is created. If this is a scalar value, the attribute is set to the value. If it is a function, then that function is called (as a method) and should return the value to be placed into that attribute. If it is a hash ref or an array ref, then that structure is COPIED in to the new object. If you don't want that, you can do something like this:
   [...]
    flat_hash => {
        attribute => {
            init_default => sub { { key => "value" } },
        },
    },
   [...]

Now, every new object will share the same hash for that attribute.

There are currently four functions that allow you to access parts of this information.

Class::Tangram::attribute_options($class)
Returns a hash ref to a data structure from attribute names to the option hash for that attribute.

Class::Tangram::attribute_types($class)
Returns a hash ref from attribute names to the tangram type for that attribute.

Class::Tangram::required_attributes($class)
Returns a hash ref from attribute names to the 'required' option setting for that attribute. May also be called as a method, as in $instance->required_attributes.

Class::Tangram::known_classes
This function returns a list of all the classes that have had their object schema imported by Class::Tangram.

Class::Tangram::is_abstract($class)
This function returns true if the supplied class is abstract.

Class->set_init_default(attribute => $value);
Sets the default value on an attribute for newly created ``Class'' objects, as if it had been declared with init_default. Can be called as a class or an instance method.


SEE ALSO

the Tangram::Schema manpage

A guided tour of Tangram, by Sound Object Logic.

 http://www.soundobjectlogic.com/tangram/guided_tour/fs.html


BUGS/TODO

More AUTOLOAD methods along the line of x_includes, in particular for container types such as array, hash, etc. Either that or encourage people to return tied variables if they want to extend that particular functionality.

There should be more functions for breaking loops; in particular, a standard function called drop_refs($obj), which replaces references to $obj with the appropriate Tangram::RefOnDemand object so that an object can be unloaded via Tangram::Storage-unload()> and actually have a hope of being reclaimed. Another function that would be handy would be a deep ``mark'' operation for manual mark & sweep garbage collection.

Need to think about writing some functions using Inline for speed. One of these days...

Allow init_default values to be set in a default import function?

ie

  use MyClassTangramObject -defaults => { foo => "bar" };


AUTHOR

Sam Vilain, <sam@vilain.net>