Class::Tangram - create constructors, accessor and update methods for objects from a Tangram-compatible object specification.
# 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)) } );
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.
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.
This is Class::Tangram version 1.10.
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.
get($attribute)
$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).
attribute($value)
$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).
set_attribute($value)
$instance->get("attribute")
and
$instance->set(attribute => $value)
, respectively.
attribute_includes(@objects)
attribute_insert(@objects)
attribute_remove(@objects)
$instance->attribute->includes(@objects)
, etc. This only
works if the attribute in question is a ``set'' or ``iset'' type.
sub attribute { $_[0]->SUPER::getset("attribute", $_[1]) }
Class::Tangram provides type checking of attributes, which are defined via three per-attribute options:
$_[0]
. It should call die()
if the value is bad.
$object->clear_refs()
), then define this.
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.
# 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_func
functions
defined for all of the standard Tangram attribute types.
m/^-?\d*(\.\d*)?(e-?\d*)?$/
). Inefficient?
Yes. Patches welcome.
isa(``Set::Object'')
Available functions:
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:
VARCHAR(N)
This function returns a string, suitable for print()ing. It does not currently escape unprintable characters.
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.
The following functions are not intended to be called as object methods.
${"${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.
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.
$instance->required_attributes
.
A guided tour of Tangram, by Sound Object Logic.
http://www.soundobjectlogic.com/tangram/guided_tour/fs.html
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" };
Sam Vilain, <sam@vilain.net>