The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Class::Tangram - magic constructors and methods for objects

SYNOPSIS

 package Orange;
 
 use vars qw(@ISA $schema);
 use Carp;
 @ISA = qw(Class::Tangram);
 
 # define the schema (ie, allowed attributes) of this object
 # See the Tangram::Schema man page for more information
 $schema = {
     table => "oranges",
 
     fields => {
         int => {
             juiciness => undef,
             segments => {
                 check_func => sub {
                     croak "too many segments"
                         if ($ {$_[0]} > SEGMENT_MAX);
                 },
                 init_default => 7;
             },
         },
         ref => {
             grower => undef,
         },
     },
 };
 
 package Project;
 
 my $dbschema = Tangram::Relational->schema
     ({ classes => [ 'Orange' => $Orange::schema ]});
 
 sub schema { $dbschema };
 
 package Main;
 
 my $storage = Tangram::Relational->connect(Project->schema, $dsn, $u, $p)
 
 # OK
 my $orange = Orange->new(juiciness => 8);
 
 my $juiciness = $orange->juiciness;   # returns 8
 
 # a "ref" must be set to a blessed object
 $grower = bless { name => "Joe" }, "Farmer";
 $orange->set_grower ($grower)
 
 # these are all illegal
 $orange->set_juiciness ("Yum");
 $orange->set_segments (SEGMENT_MAX + 1);
 $orange->set_grower ("The Fabulous Furry Freak Brothers");
 
 # if you prefer
 $orange->get( "juiciness" );
 $orange->set( juiciness => 123 );

DESCRIPTION

Class::Tangram is a 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 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 writing general classes.

METHODS

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.

check_X (\$value)

This series of functions checks that $value is of the type X, and within applicable bounds. If there is a problem, then it will croak() the error. These functions are not called from the code, but by the set() method on a particular attribute.

Available functions are:

  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
                 (m/^\d*(\.\d*)?(e\d*)?$/)
  check_obj    - checks that the supplied variable is a
                 reference to a blessed object
  check_flat_array
               - checks that $value is a ref ARRAY
  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_rawdatetime 
               - checks that $value is of the form
                 YYYY-MM-DD HH:MM:SS
  check_time
              - checks that $value is of the form
                HH:MM(:SS)?
  check_timestamp
              - checks that $value is of the form
                (YYYY-MM-DD )?HH:MM(:SS)?
  check_flat_hash
               - checks that $value is a ref HASH
  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 are:

  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.  Contains a hack for
                  Tangram so that if this ref is not loaded,
                  it will not be autoloaded when it is
                  attempted to be accessed.
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 unmanagably unportable across databases. This 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
      TINYTEXT, TEXT, LONGTEXT
          checks max. length of string to be 255,
          65535 or 16777215 chars respectively
      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.
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.

This function updates Tangram schema option hashes, with the following keys:

  check_func   - supply/override the check_X function for
                 this attribute.

  destroy_func - supply/override the destroy_X function for
                 this attribute

See the SYNOPSIS section for an example of supplying a check_func in an object schema.

$instance->set(attribute => value, ...)

Sets the attributes of the given instance to the given values

$instance->get($attribute)

Gets the value of $attribute

$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->attribute($value)

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

After a little thought I decided that perhaps it would be better to make the semantics $instance->set_attribute($value) for the case of set, and optionally $instance->get_attribute(). This makes the code read better, because your sentence of code gains a verb.

$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]) }
$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.

SEE ALSO

Tangram::Schema

A guided tour of Tangram, by Sound Object Logic.

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

BUGS/TODO

More datetime types. I avoided the DMDateTime type because Date::Manip is self-admittedly the most bloated module on CPAN, and I don't want to be seen encouraging it :-)

This documentation should be easy enough for a fool to understand.

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 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 mark & sweep garbage collection.

AUTHOR

Sam Vilain, <sam@vilain.net>