/[cvs]/nfo/perl/libs/Class/Tangram.pm
ViewVC logotype

Diff of /nfo/perl/libs/Class/Tangram.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.2 by joko, Thu Oct 17 02:34:06 2002 UTC revision 1.3 by joko, Thu Oct 17 02:34:45 2002 UTC
# Line 1  Line 1 
1  package Class::Tangram;  package Class::Tangram;
2    
3  # Copyright (c) 2001 Sam Vilain. All rights reserved. This program is  # Copyright (c) 2001, 2002 Sam Vilain.  All right reserved.  This file
4  # free software; you can redistribute it and/or modify it under the  # is licensed under the terms of the Perl Artistic license.
 # same terms as Perl itself.  
   
 # Some modifications  
 # $Id$  
 # Copyright 2001 Micro Sharp Technologies, Inc., Vancouver, WA, USA  
 # Author: Karl M. Hegbloom <karlheg@microsharp.com>  
 # Perl Artistic Licence.  
5    
6  =head1 NAME  =head1 NAME
7    
# Line 17  objects from a Tangram-compatible object Line 10  objects from a Tangram-compatible object
10    
11  =head1 SYNOPSIS  =head1 SYNOPSIS
12    
13   package Orange;   package MyObject;
14    
15   use base qw(Class::Tangram);   use base qw(Class::Tangram);
  use vars qw($schema);  
  use Tangram::Ref;  
16    
17   # define the schema (ie, allowed attributes) of this object.  See the   our $fields => { int    => [ qw(foo bar) ],
18   # Tangram::Schema man page for more information on the syntax here.                    string => [ qw(baz quux) ] };
  $schema = {  
      table => "oranges",  
   
      fields => {  
          int => {  
              juiciness => undef,  
              segments => {  
                  # here is a new one - this code reference is called  
                  # when this attribute is set; it should die() on  
                  # error, as it is wrapped in an eval { } block  
                  check_func => sub {  
                      die "too many segments"  
                          if ($ {$_[0]} > 30);  
                  },  
                  # the default for this attribute.  
                  init_default => 7,  
              },  
          },  
          ref => {  
              grower => undef,  
          },  
      },  
  };  
  Class::Tangram::import_schema("Orange");  
19    
20   package Project;   package main;
  # 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;  
21    
22   my $dbschema = Tangram::Schema->new   my $object = MyObject->new(foo => 2, baz => "hello");
      ({ classes => [ 'Orange' => $Orange::schema ]});  
23    
24   sub schema { $dbschema };   print $object->baz();            # prints "hello"
25    
26   package main;   $object->set_quux("Something");
27    
28   # See Tangram::Relational for instructions on using "deploy" to   $object->set_foo("Something");   # dies - not an integer
  # 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);  
29    
30   # OK  =head1 DESCRIPTION
  my $orange = Orange->new(juiciness => 8);  
  my $juiciness = $orange->juiciness;   # returns 8  
31    
32   # a "ref" must be set to a blessed object  Class::Tangram is a tool for defining objects attributes.  Simply
33   my $grower = bless { name => "Joe" }, "Farmer";  define your object's fields/attributes using the same syntax
34   $orange->set_grower ($grower);  introduced in _A Guided Tour of Tangram_, and you get objects that
35    work As You'd Expect(tm).
36    
37    Class::Tangram has no dependancy upon Tangram, and vice versa.
38    Neither requires anything special of your objects, nor do they insert
39    any special fields into your objects.  This is a very important
40    feature with innumerable benefits, and few (if any) other object
41    persistence tools have this feature.
42    
43    So, fluff aside, let's run through how you use Class::Tangram to make
44    objects.
45    
46    First, you decide upon the attributes your object is going to have.
47    You might do this using UML, or you might pick an existing database
48    table and declare each column to be an attribute (you can leave out
49    "id"; that one is implicit).
50    
51    Your object should use Class::Tangram as a base class;
52    
53      use base qw(Class::Tangram)
54    
55    or for older versions of perl:
56    
57      use Class::Tangram;
58      use vars qw(@ISA);
59      @ISA = qw(Class::Tangram)
60    
61    You should then define a C<$fields> variable in the scope of the
62    package, that is a B<hash> from attribute B<types> (see
63    L<Tangram::Type>) to either an B<array> of B<attribute names>, or
64    another B<hash> from B<attribute names> to B<options hashes> (or
65    C<undef>).  The layout of this structure coincides with the C<fields>
66    portion of a tangram schema (see L<Tangram::Schema>).  Note: the term
67    `schema' is used frequently to refer to the C<$fields> structure.
68    
69   # these are all illegal  For example,
  eval { $orange->set_juiciness ("Yum"); }; print $@;  
  eval { $orange->set_segments (31); }; print $@;  
  eval { $orange->set_grower ("Mr. Nice"); }; print $@;  
70    
71   # if you prefer   package MyObject;
72   $orange->get( "juiciness" );   use base qw(Class::Tangram);
  $orange->set( juiciness => 123 );  
73    
74  =head1 DESCRIPTION   our $fields = {
75         int => {
76             juiciness => undef,
77             segments => {
78                 # this code reference is called when this
79                 # attribute is set, to check the value is
80                 # OK
81                 check_func => sub {
82                     die "too many segments"
83                         if (${(shift)} > 30);
84                 },
85                 # the default for this attribute.
86                 init_default => 7,
87             },
88         },
89         ref => {
90            grower => undef,
91         },
92    
93         # 'required' attributes - insist that these fields are
94         # set, both with constructor and set()/set_X methods
95         string => {
96             # true: 'type' must have non-empty value (for
97             # strings) or be logically true (for other types)
98             type => { required => 1 },
99    
100             # false: 'tag' must be defined but may be empty
101             tag => { required => '' },
102         },
103    
104         # fields allowed by Class::Tangram but not ever
105         # stored by Tangram - no type checking by default
106         transient => [ qw(_tangible) ],
107     };
108    
109    It is of critical importance to your sanity that you understand how
110    anonymous hashes and anonymous arrays work in Perl.  Some additional
111    features are used above that have not yet been introduced, but you
112    should be able to look at the above data structure and see that it
113    satisfies the conditions stated in the paragraph before it.  If it is
114    hazy, I recommend reading L<perlref> or L<perlreftut>.
115    
116    When the schema for the object is first imported (see L<Schema
117    import>), Class::Tangram defines accessor functions for each of the
118    attributes defined in the schema.  These accessor functions are then
119    available as C<$object-E<gt>function> on created objects.  By virtue
120    of inheritance, various other methods are available.
121    
122  Class::Tangram is a base class originally intended for use with  From Class::Tangram 1.12 onwards, no use of perl's C<AUTOLOAD>
123  Tangram objects, that gives you free constructors, access methods,  functionality is used.
 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 C<$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.  
124    
125  =cut  =cut
126    
127  use strict;  use strict;
128  use Carp qw(croak cluck);  use Carp qw(croak cluck);
129    
130  use vars qw($AUTOLOAD $VERSION);  use vars qw($VERSION %defaults);
 $VERSION = "1.04";  
131    
132  local $AUTOLOAD;  use Set::Object;
133    
134    use Pod::Constants -trim => 1,
135        'MODULE RELEASE' => sub { ($VERSION) = /(\d+\.\d+)/ },
136        'Default Type Checking' => sub { %defaults = eval; };
137    
138  # $types{$class}->{$attribute} is the tangram type of each attribute  # $types{$class}->{$attribute} is the tangram type of each attribute
139  my (%types);  my (%types);
140    
141    # $attribute_options{$class}->{$attribute} is the hash passed to tangram
142    # for the given attribute
143    my (%attribute_options);
144    
145  # $check{$class}->{$attribute}->($value) is a function that will die  # $check{$class}->{$attribute}->($value) is a function that will die
146  # if $value is not alright, see check_X functions  # if $value is not alright, see check_X functions
147  my (%check);  my (%check);
# Line 128  my (%cleaners); Line 155  my (%cleaners);
155  # $init_defaults{$class}->{$attribute}  # $init_defaults{$class}->{$attribute}
156  my (%init_defaults);  my (%init_defaults);
157    
158    # $required_attributes{$class}->{$attribute} records which attributes
159    # are required... used only by new() at present.
160    my (%required_attributes);
161    
162  # if a class is abstract, complain if one is constructed.  # if a class is abstract, complain if one is constructed.
163  my (%abstract);  my (%abstract);
164    
165  =head1 METHODS  =head1 METHODS
166    
167    The following methods are available for all Class::Tangram objects
168    
169    =head2 Constructor
170    
171    A Constructor is a method that returns a new instance of an object.
172    
173  =over 4  =over 4
174    
175  =item Class-E<gt>new (attribute1 =E<gt> value, attribute2 =E<gt> value)  =item Class-E<gt>new (attribute1 =E<gt> value, attribute2 =E<gt> value)
176    
177  sets up a new object of type Class, with attributes set to the values  Sets up a new object of type C<Class>, with attributes set to the
178  supplied.  values supplied.
179    
180  Can also be used as an object method, in which case it returns a  Can also be used as an object method (normal use is as a "class
181  B<copy> of the object, without any deep copying.  method"), in which case it returns a B<copy> of the object, without
182    any deep copying.
183    
184  =cut  =cut
185    
# Line 150  sub new ($@) Line 188  sub new ($@)
188      my $invocant = shift;      my $invocant = shift;
189      my $class = ref $invocant || $invocant;      my $class = ref $invocant || $invocant;
190    
191      my @values = @_;      (my @values, @_) = @_;
192    
193      # Setup the object      # Setup the object
194      my $self = { };      my $self = { };
195      bless $self, $class;      bless $self, $class;
196    
197      exists $check{$class} or import_schema($class);      # auto-load schema as necessary
198        exists $types{$class} or import_schema($class);
199    
200      croak "Attempt to instantiate an abstract type"      croak "Attempt to instantiate an abstract type"
201          if ($abstract{$class});          if ($abstract{$class});
202    
203      if ($invocant ne $class)      if (ref $invocant)
204      {      {
205          # The copy constructor; this could be better :)          # The copy constructor; this could be better :)
206          # this has the side effect of much auto-vivification.          # this has the side effect of much auto-vivification.
# Line 171  sub new ($@) Line 210  sub new ($@)
210      else      else
211      {      {
212          $self->set (@values); # start with @values          $self->set (@values); # start with @values
213        }
214    
215        $self->_fill_init_default();
216        $self->_check_required();
217    
218          # now fill in fields that have defaults      return $self;
219          for my $attribute (keys %{$init_defaults{$class}}) {  
220    }
221    
222              next if (exists $self->{$attribute});  sub _fill_init_default {
223        my $self = shift;
224        my $class = ref $self or die "_fill_init_default usage error";
225    
226        # fill in fields that have defaults
227        while ( my ($attribute, $default) =
228                each %{$init_defaults{$class}} ) {
229    
230            next if (exists $self->{$attribute});
231    
232            my $setter = "set_$attribute";
233            if (ref $default eq "CODE") {
234                # sub { }, attribute gets return value
235                $self->$setter( $default->($self) );
236    
237            } elsif (ref $default eq "HASH") {
238                # hash ref, copy hash
239                $self->$setter( { %{ $default } } );
240    
241            } elsif (ref $default eq "ARRAY") {
242                # array ref, copy array
243                $self->$setter( [ @{ $default } ] );
244    
245              my $default = $init_defaults{$class}->{$attribute}          } else {
246                  unless tied $init_defaults{$class}->{$attribute};              # something else, an object or a scalar
247                $self->$setter($default);
248            }
249        }
250    }
251    
252              if (ref $default eq "CODE") {  sub _check_required {
253                  # sub { }, attribute gets return value      my $self = shift;
254                  $self->{$attribute}      my $class = ref $self;
255                      = $init_defaults{$class}->{$attribute}->();  
256        # make sure field is not undef if 'required' option is set
257              } elsif (ref $default eq "HASH") {      if (my $required = $required_attributes{$class}) {
258                  # hash ref, copy hash  
259                  $self->{$attribute}          # find the immediate caller outside of this package
260                      = { %{ $init_defaults{$class}->{$attribute} } };          my $i = 0;
261            $i++ while UNIVERSAL::isa($self, scalar(caller($i))||";->");
262              } elsif (ref $default eq "ARRAY") {  
263                  # array ref, copy array          # give Tangram some lenience - it is exempt from the effects
264                  $self->{$attribute}          # of the "required" option
265                      = [ @{ $init_defaults{$class}->{$attribute} } ];          unless ( caller($i) =~ m/^Tangram::/ ) {
266                my @missing;
267              } else {              while ( my ($attribute, $value) = each %$required ) {
268                  # something else, an object or a scalar                  push(@missing, $attribute)
269                  $self->{$attribute}                      if ! exists $self->{$attribute};
                     = $init_defaults{$class}->{$attribute};  
270              }              }
271                croak("object missing required attribute(s): "
272                      .join(', ',@missing).'.') if @missing;
273          }          }
274      }      }
     return $self;  
275  }  }
276    
277    =back
278    
279    =head2 Accessing & Setting Attributes
280    
281    =over
282    
283  =item $instance->set(attribute => $value, ...)  =item $instance->set(attribute => $value, ...)
284    
285  Sets the attributes of the given instance to the given values.  croaks  Sets the attributes of the given instance to the given values.  croaks
286  if there is a problem with the values.  if there is a problem with the values.
287    
288    This function simply calls C<$instance-E<gt>set_attribute($value)> for
289    each of the C<attribute =E<gt> $value> pairs passed to it.
290    
291  =cut  =cut
292    
293  sub set($@) {  sub set {
294      my ($self, @values) = (@_);      my $self = shift;
295    
296      # yes, this is a lot to do.  yes, it's slow.  But I'm fairly      # yes, this is a lot to do.  yes, it's slow.  But I'm fairly
297      # certain that this could be handled efficiently if it were to be      # certain that this could be handled efficiently if it were to be
298      # moved inside the Perl interpreter or an XS module      # moved inside the Perl interpreter or an XS module
299      $self->isa("Class::Tangram") or croak "type mismatch";      UNIVERSAL::isa($self, "Class::Tangram") or croak "type mismatch";
300      my $class = ref $self;      my $class = ref $self;
301      exists $check{$class} or import_schema($class);      exists $check{$class} or import_schema($class);
302        croak "set must be called with an even number of arguments"
303            if (scalar(@_) & 1);
304    
305      while (my ($name, $value) = splice @values, 0, 2) {      while (my ($name, $value) = splice @_, 0, 2) {
         croak "attempt to set an illegal field $name in a $class"  
             if (!defined $check{$class}->{$name});  
306    
307          #local $@;          my $setter = "set_".$name;
308    
309          # these handlers die on failure          croak "attempt to set an illegal field $name in a $class"
310          eval { $check{$class}->{$name}->(\$value) };              unless $self->can($setter);
         $@ && croak ("value failed type check - ${class}->{$name}, "  
                      ."\"$value\" ($@)");  
311    
312          #should be ok now          $self->$setter($value);
         $self->{$name} = $value;  
313      }      }
314  }  }
315    
316  =item $instance->get($attribute)  =item $instance->get("attribute")
317    
318  Gets the value of $attribute.  If the attribute in question is a set,  Gets the value of C<$attribute>.  This simply calls
319  and this method is called in list context, then it returns the MEMBERS  C<$instance-E<gt>get_attribute>.  If multiple attributes are listed,
320  of the set (if called in scalar context, it returns the Set::Object  then a list of the attribute values is returned in order.  Note that
321  container).  you get back the results of the scalar context C<get_attribute> call
322    in this case.
323    
324  =cut  =cut
325    
326  sub get($$) {  sub get {
327      my ($self, $field) = (@_);      my $self = shift;
328      $self->isa("Class::Tangram") or croak "type mismatch";      croak "get what?" unless @_;
329        UNIVERSAL::isa($self, "Class::Tangram") or croak "type mismatch";
330    
331      my $class = ref $self;      my $class = ref $self;
332      exists $check{$class} or import_schema($class);      exists $check{$class} or import_schema($class);
     croak "attempt to read an illegal field $field in a $class"  
         if (!defined $check{$class}->{$field});  
333    
334      if ($types{$class}->{$field} =~ m/^i?set$/o) {      my $multiget = (scalar(@_) != 1);
335          if (!defined $self->{$field}) {  
336              $self->{$field} = Set::Object->new();      my @return;
337          }      while ( my $field = shift ) {
338          if (wantarray) {          my $getter = "get_".$field;
339              return $self->{$field}->members;          croak "attempt to read an illegal field $field in a $class"
340                unless $self->can($getter);
341    
342            if ( $multiget ) {
343                push @return, scalar($self->$getter());
344            } else {
345                return $self->$getter();
346          }          }
347      }      }
348    
349      return $self->{$field};      return @return;
350  }  }
351    
352  =item $instance->attribute($value)  =item $instance->attribute($value)
353    
354  If $value is not given, then  If C<$value> is not given, then
355  this is equivalent to $instance->get("attribute")  this is equivalent to C<$instance-E<gt>get_attribute>
356    
357  If $value is given, then this is equivalent to  If C<$value> is given, then this is equivalent to
358  $instance->set("attribute", $value).  This usage issues a warning; you  C<$instance-E<gt>set_attribute($value)>.  This usage issues a warning
359  should change your code to use the set_attribute syntax for better  if warnings are on; you should change your code to use the
360  readability.  set_attribute syntax for better readability.  OO veterans will tell
361    you that for maintainability object method names should always be a
362    verb.
363    
364  =item $instance->get_attribute  =item $instance->get_attribute
365    
366  =item $instance->set_attribute($value)  =item $instance->set_attribute($value)
367    
368  Equivalent to $instance->get("attribute") and $instance->set(attribute  The normative way of getting and setting attributes.  If you wish to
369  => $value), respectively.  override the behaviour of an object when getting or setting an
370    attribute, override these functions.  They will be called when you use
371    C<$instance-E<gt>attribute>, C<$instance-E<gt>get()>, constructors,
372    etc.
373    
374  =item $instance->attribute_includes(@objects)  =item $instance->attribute_includes(@objects)
375    
# Line 294  Equivalent to $instance->get("attribute" Line 381  Equivalent to $instance->get("attribute"
381    
382  =item $instance->attribute_remove(@objects)  =item $instance->attribute_remove(@objects)
383    
384  Equivalent to calling $instance->attribute->includes(@objects), etc.  This suite of functions applies to attributes that are sets (C<iset>
385  This only works if the attribute in question is a Set::Object.  or C<set>).  It could in theory also apply generally to all
386    collections - ie also arrays (C<iarray> or C<array>), and hashes
387    (C<hash>, C<ihash>).  This will be implemented subject to user demand.
388    
389    =back
390    
391    B<Note:> The above functions can be overridden, but they may not be
392    called with the C<$self-E<gt>SUPER::> superclass chaining method.
393    This is because they are not defined within the scope of
394    Class::Tangram, only your package.
395    
396  =cut  =cut
397    
398  sub AUTOLOAD ($;$) {  =head1 ATTRIBUTE TYPE CHECKING
     my ($self, $value) = (@_);  
     $self->isa("Class::Tangram") or croak "type mismatch";  
399    
400      my $class = ref $self;  Class::Tangram provides type checking of attributes when attributes
401      $AUTOLOAD =~ s/.*://;  are set - either using the default C<set_attribute> functions, or
402      if ($AUTOLOAD =~ m/^(set_|get_)?([^:]+)$/  created via the C<new> constructor.
         and defined $types{$class}->{$2}) {  
   
         # perl sucks at this type of test  
         if ((defined $1 and $1 eq "set_")  
             or (!defined $1 and defined $value)) {  
   
             if ($^W && !defined $1) {  
                 cluck("The OO police say change your call to "  
                       ."\$obj->set_$2");  
             }  
             return set($self, $2, $value);  
         } else {  
             return get($self, $2);  
         }  
     } elsif (my ($attr, $method) =  
              ($AUTOLOAD =~ m/^(.*)_(includes|insert|  
                              size|clear|remove)$/x)  
              and $types{$class}->{$1} =~ m/^i?set$/) {  
         return get($self, $attr)->$method(@_[1..$#_]);  
     } else {  
         croak("unknown method/attribute ${class}->$AUTOLOAD called");  
     }  
 }  
403    
404  =item $instance->getset($attribute, $value)  The checking has default behaviour for each type of attribute (see
405    L<Default Type Checking>), and can be extended arbitrarily via a
406    per-attribute C<check_func>, described below.  Critical attributes can
407    be marked as such with the C<required> flag.
408    
409  If you're replacing the AUTOLOAD function in your Class::Tangram  The specification of this type checking is placed in the class schema,
410  derived class, but would still like to use the behaviour for one or  in the per-attribute B<options hash>.  This is a Class::Tangram
411  two fields, then you can define functions for them to fall through to  extension to the Tangram schema structure.
 the Class::Tangram method, like so:  
412    
413   sub attribute { $_[0]->SUPER::getset("attribute", $_[1]) }  =over
414    
415  =cut  =item check_func
416    
417  sub getset($$;$) {  A function that is called with a B<reference> to the new value in
418      my ($self, $attr, $value) = (@_);  C<$_[0]>.  It should call C<die()> if the value is bad.  Note that
419      $self->isa("Class::Tangram") or croak "type mismatch";  this check_func will never be passed an undefined value; this is
420    covered by the "required" option, below.
421    
422      if (defined $value) {  In the example schema (above), the attribute C<segments> has a
423          return set($self, $attr, $value);  C<check_func> that prevents setting the value to anything greater than
424      } else {  30.  Note that it does not prevent you from setting the value to
425          return get($self, $attr);  something that is not an integer; if you define a C<check_func>, it
426      }  replaces the default.
427    
428  }  =item required
429    
430    If this option is set to a true value, then the attribute must be set
431    to a true value to pass type checking.  For string attributes, this
432    means that the string must be defined and non-empty (so "0" is true).
433    For other attribute types, the normal Perl definition of logical truth
434    is used.
435    
436    If the required option is defined but logically false, (ie "" or 0),
437    then the attribute must also be defined, but may be set to a logically
438    false value.
439    
440    If the required option is undefined, then the attribute may be set to
441    an undefined value.
442    
443    For integration with tangram, the C<new()> function has a special
444    hack; if it is being invoked from within Tangram, then the required
445    test is skipped.
446    
447    =back
448    
449    =head2 Other per-attribute options
450    
451    Any of the following options may be inserted into the per-attribute
452    B<options hash>:
453    
454    =over
455    
456    =item init_default
457    
458    This value specifies the default value of the attribute when
459    it is created with C<new()>.  It is a scalar value, it is
460    copied to the fresh object.  If it is a code reference, that
461    code reference is called and its return value inserted into
462    the attribute.  If it is an ARRAY or HASH reference, then
463    that array or hash is COPIED into the attribute.
464    
465    =item destroy_func
466    
467    If anything special needs to happen to this attribute before the
468    object is destroyed (or when someone calls
469    C<$object-E<gt>clear_refs()>), then define this.  It is called as
470    C<$sub-E<gt>($object, "attribute")>.
471    
472    =back
473    
474    =head2 Default Type Checking
475    
476     # The following list is eval'ed from this documentation
477     # when Class::Tangram loads, and used as default attribute
478     # options for the specified types.  So, eg, the default
479     # "init_default" for "set" types is a subroutine that
480     # returns a new Set::Object container.
481    
482     # "parse" is special - it is passed the options hash given
483     # by the user and should return (\&check_func,
484     # \&destroy_func).  This is how the magical string type
485     # checking is performed - see the entry for parse_string(),
486     # below.
487    
488     int         => { check_func   => \&check_int },
489     real        => { check_func   => \&check_real },
490     string      => { parse        => \&parse_string },
491     ref         => { check_func   => \&check_obj,
492                      destroy_func => \&destroy_ref },
493     array       => { check_func   => \&check_array,
494                      destroy_func => \&destroy_array },
495     iarray      => { check_func   => \&check_array,
496                      destroy_func => \&destroy_array },
497     flat_array  => { check_func   => \&check_flat_array },
498     set         => { check_func   => \&check_set,
499                      destroy_func => \&destroy_set,
500                      init_default => sub { Set::Object->new() } },
501     iset        => { check_func   => \&check_set,
502                      destroy_func => \&destroy_set,
503                      init_default => sub { Set::Object->new() } },
504     dmdatetime  => { check_func   => \&check_dmdatetime },
505     rawdatetime => { check_func   => \&check_rawdatetime },
506     rawdate     => { check_func   => \&check_rawdate },
507     rawtime     => { check_func   => \&check_rawtime },
508     flat_hash   => { check_func   => \&check_flat_hash },
509     transient   => { check_func   => \&check_nothing },
510     hash        => { check_func   => \&check_hash,
511                      destroy_func => \&destroy_hash,
512                      get_func     => \&get_hash },
513     perl_dump   => { check_func   => \&check_nothing }
514    
515    =over
516    
517  =item check_X (\$value)  =item check_X (\$value)
518    
519  This series of functions checks that $value is of the type X, and  This series of internal functions are built-in C<check_func> functions
520  within applicable bounds.  If there is a problem, then it will croak()  defined for all of the standard Tangram attribute types.
521  the error.  These functions are not called from the code, but by the  
522  set() method on a particular attribute.  =over
523    
524  Available functions are:  =item check_string
525    
526    check_string - checks that the supplied value is less  checks that the supplied value is less than 255 characters long.
                  than 255 characters long.  
527    
528  =cut  =cut
529    
530  sub check_string {  sub check_string {
531      croak "string ${$_[0]} too long"      croak "string too long"
532          if (length ${$_[0]} > 255);          if (length ${$_[0]} > 255);
533  }  }
534    
535  =pod  =item check_int
536    
537    check_int    - checks that the value is a (possibly  checks that the value is a (possibly signed) integer
                  signed) integer  
538    
539  =cut  =cut
540    
541  my $int_re = qr/^-?\d+$/;  my $int_re = qr/^-?\d+$/;
542  sub check_int {  sub check_int {
543      croak "not an int"      croak "not an integer"
544          if (${$_[0]} !~ m/$int_re/ms);          if (${$_[0]} !~ m/$int_re/o);
545  }  }
546    
547  =pod  =item check_real
548    
549    check_real   - checks that the value is a real number  checks that the value is a real number, by stringifying it and
550                   (m/^\d*(\.\d*)?(e\d*)?$/)  matching it against (C<m/^-?\d*(\.\d*)?(e-?\d*)?$/>).  Inefficient?
551    Yes.  Patches welcome.
552    
553  =cut  =cut
554    
555  my $real_re = qr/^-?\d*(\.\d*)?(e-?\d*)?$/;  my $real_re = qr/^-?\d*(\.\d*)?(e-?\d*)?$/;
556  sub check_real {  sub check_real {
557      croak "not a real"      croak "not a real number"
558          if (${$_[0]} !~ m/$real_re/ms);          if (${$_[0]} !~ m/$real_re/o);
559  }  }
560    
561  =pod  =item check_obj
562    
563    check_obj    - checks that the supplied variable is a  checks that the supplied variable is a reference to a blessed object
                  reference to a blessed object  
564    
565  =cut  =cut
566    
# Line 409  sub check_real { Line 568  sub check_real {
568  my $obj_re = qr/^(?:HASH|ARRAY|SCALAR)?$/;  my $obj_re = qr/^(?:HASH|ARRAY|SCALAR)?$/;
569  sub check_obj {  sub check_obj {
570      croak "not an object reference"      croak "not an object reference"
571          if ((ref ${$_[0]}) =~ m/$obj_re/);          if ((ref ${$_[0]}) =~ m/$obj_re/o);
572  }  }
573    
574  =pod  =item check_flat_array
575    
576    check_flat_array  checks that $value is a ref ARRAY and that all elements are unblessed
577                 - checks that $value is a ref ARRAY  scalars.  Does NOT currently check that all values are of the correct
578    type (int vs real vs string, etc)
579    
580  =cut  =cut
581    
582  sub check_flat_array {  sub check_flat_array {
583      croak "not a flat array"      croak "not a flat array"
584          if (ref ${$_[0]} ne "ARRAY");          if (ref ${$_[0]} ne "ARRAY");
585        croak "flat array may not contain references"
586            if (map { (ref $_ ? "1" : ()) } @{$_[0]});
587  }  }
588    
589  =pod  =item check_array
590    
591    check_array  - checks that $value is a ref ARRAY, and that  checks that $value is a ref ARRAY, and that each element in the array
592                   each element in the array is a reference to  is a reference to a blessed object.
                  a blessed object.  
593    
594  =cut  =cut
595    
# Line 437  sub check_array { Line 598  sub check_array {
598          if (ref ${$_[0]} ne "ARRAY");          if (ref ${$_[0]} ne "ARRAY");
599      for my $a (@{${$_[0]}}) {      for my $a (@{${$_[0]}}) {
600          croak "member in array not an object reference"          croak "member in array not an object reference"
601              if ((ref $a) =~ m/$obj_re/);              if ((ref $a) =~ m/$obj_re/o);
602      }      }
603  }  }
604    
605  =pod  =item check_set
606    
607    check_set    - checks that $value->isa("Set::Object")  checks that $value->isa("Set::Object")
608    
609  =cut  =cut
610    
611  sub check_set {  sub check_set {
612      croak "set type not passed a Set::Object"      croak "set type not passed a Set::Object"
613          unless (ref ${$_[0]} and ${$_[0]}->isa("Set::Object"));          unless (UNIVERSAL::isa(${$_[0]}, "Set::Object"));
614  }  }
615    
616  =pod  =item check_rawdate
617    
618    check_rawdatetime  checks that $value is of the form YYYY-MM-DD, or YYYYMMDD, or YYMMDD.
                - checks that $value is of the form  
                  YYYY-MM-DD HH:MM:SS  
619    
620  =cut  =cut
621    
622  # YYYY-MM-DD HH:MM:SS  # YYYY-MM-DD HH:MM:SS
623  my $rawdatetime_re = qr/^\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}:\d{2}$/;  my $rawdate_re = qr/^(?:  \d{4}-\d{2}-\d{2}
624  sub check_rawdatetime {                       |    (?:\d\d){3,4}
625      croak "invalid SQL rawdatetime"                       )$/x;
626          unless (${$_[0]} =~ m/$rawdatetime_re/);  sub check_rawdate {
627        croak "invalid SQL rawdate"
628            unless (${$_[0]} =~ m/$rawdate_re/o);
629  }  }
630    
631  =pod  =item check_rawtime
632    
633    check_time  checks that $value is of the form HH:MM(:SS)?
               - checks that $value is of the form  
                 HH:MM(:SS)?  
634    
635  =cut  =cut
636    
637  my $time_re = qr/^\d{1,2}:\d{2}(?::\d{2})?$/;  # YYYY-MM-DD HH:MM:SS
638  sub check_time {  my $rawtime_re = qr/^\d{1,2}:\d{2}(?::\d{2})?$/;
639      croak "invalid SQL time"  sub check_rawtime {
640          unless (${$_[0]} =~ m/$time_re/);      croak "invalid SQL rawtime"
641            unless (${$_[0]} =~ m/$rawtime_re/o);
642    }
643    
644    =item check_rawdatetime
645    
646    checks that $value is of the form YYYY-MM-DD HH:MM(:SS)? (the time
647    and/or the date can be missing), or a string of numbers between 6 and
648    14 numbers long.
649    
650    =cut
651    
652    my $rawdatetime_re = qr/^(?:
653                                # YYYY-MM-DD HH:MM:SS
654                                (?: (?:\d{4}-\d{2}-\d{2}\s+)?
655                                    \d{1,2}:\d{2}(?::\d{2})?
656                                |   \d{4}-\d{2}-\d{2}
657                                )
658                             |  # YYMMDD, etc
659                                (?:\d\d){3,7}
660                             )$/x;
661    sub check_rawdatetime {
662        croak "invalid SQL rawdatetime dude"
663            unless (${$_[0]} =~ m/$rawdatetime_re/o);
664  }  }
665    
666  =pod  =item check_dmdatetime
667    
668    check_timestamp  checks that $value is of the form YYYYMMDDHH:MM:SS, or those allowed
669                - checks that $value is of the form  for rawdatetime.
                 (YYYY-MM-DD )?HH:MM(:SS)?  
670    
671  =cut  =cut
672    
673  my $timestamp_re = qr/^(?:\d{4}-\d{2}-\d{2}\s+)?\d{1,2}:\d{2}(?::\d{2})?$/;  sub check_dmdatetime {
674  sub check_timestamp {      croak "invalid SQL rawdatetime dude"
675      croak "invalid SQL timestamp"          unless (${$_[0]} =~ m/^\d{10}:\d\d:\d\d$|$rawdatetime_re/o);
         unless (${$_[0]} =~ m/$timestamp_re/);  
676  }  }
677    
678  =pod  =item check_flat_hash
679    
680    check_flat_hash  checks that $value is a ref HASH and all values are scalars.  Does NOT
681                 - checks that $value is a ref HASH  currently check that all values are of the correct type (int vs real
682    vs string, etc)
683    
684  =cut  =cut
685    
# Line 511  sub check_flat_hash { Line 692  sub check_flat_hash {
692      }      }
693  }  }
694    
695  =pod  =item check_hash
696    
697    check_hash   - checks that $value is a ref HASH, that  checks that $value is a ref HASH, that every key in the hash is a
698                   every key in the hash is a scalar, and that  scalar, and that every value is a blessed object.
                  every value is a blessed object.  
699    
700  =cut  =cut
701    
# Line 530  sub check_hash { Line 710  sub check_hash {
710      }      }
711  }  }
712    
713  =pod  =item check_nothing
714    
715    check_nothing - checks whether Australians like sport  checks whether Australians like sport
716    
717  =cut  =cut
718    
719  sub check_nothing { }  sub check_nothing { }
720    
721    =back
722    
723  =item destroy_X ($instance, $attr)  =item destroy_X ($instance, $attr)
724    
725  Similar story with the check_X series of functions, these are called  Similar story with the check_X series of functions, these are called
# Line 548  destroyed also>.  In other words, do not Line 730  destroyed also>.  In other words, do not
730  share Set::Object containers or hash references in their attributes,  share Set::Object containers or hash references in their attributes,
731  otherwise when one gets destroyed the others will lose their data.  otherwise when one gets destroyed the others will lose their data.
732    
733  Available functions are:  Available functions:
734    
735    =over
736    
737    destroy_array - empties an array  =item destroy_array
738    
739    empties an array
740    
741  =cut  =cut
742    
743  sub destroy_array {  sub destroy_array {
744      my ($self, $attr) = (@_);      my $self = shift;
745        my $attr = shift;
746      my $t = tied $self->{$attr};      my $t = tied $self->{$attr};
747      @{$self->{$attr}} = () unless ($t =~ m,Tangram::CollOnDemand,);      @{$self->{$attr}} = ()
748            unless (defined $t and $t =~ m,Tangram::CollOnDemand,);
749      delete $self->{$attr};      delete $self->{$attr};
750  }  }
751    
752  =pod  =item destroy_set
753    
754    destroy_set   - calls Set::Object::clear to clear the set  Calls Set::Object::clear to clear the set
755    
756  =cut  =cut
757    
758  sub destroy_set {  sub destroy_set {
759      my ($self, $attr) = (@_);      my $self = shift;
760        my $attr = shift;
     # warnings suck sometimes  
     local $^W = 0;  
761    
762      my $t = tied $self->{$attr};      my $t = tied $self->{$attr};
763      return if ($t =~ m,Tangram::CollOnDemand,);      return if (defined $t and $t =~ m,Tangram::CollOnDemand,);
764      if (ref $self->{$attr} eq "Set::Object") {      if (ref $self->{$attr} eq "Set::Object") {
765          $self->{$attr}->clear;          $self->{$attr}->clear;
766      }      }
767      delete $self->{$attr};      delete $self->{$attr};
768  }  }
769    
770  =pod  =item destroy_hash
771    
772    destroy_hash  - empties a hash  empties a hash
773    
774  =cut  =cut
775    
776  sub destroy_hash {  sub destroy_hash {
777      my ($self, $attr) = (@_);      my $self = shift;
778        my $attr = shift;
779      my $t = tied $self->{$attr};      my $t = tied $self->{$attr};
780      %{$self->{$attr}} = () unless ($t =~ m,Tangram::CollOnDemand,);      %{$self->{$attr}} = ()
781            unless (defined $t and $t =~ m,Tangram::CollOnDemand,);
782      delete $self->{$attr};      delete $self->{$attr};
783  }  }
784    
785  =pod  =item destroy_ref
786    
787    destroy_ref   - destroys a reference.  Contains a hack for  destroys a reference.
                   Tangram so that if this ref is not loaded,  
                   it will not be autoloaded when it is  
                   attempted to be accessed.  
788    
789  =cut  =cut
790    
791  sub destroy_ref {  sub destroy_ref {
792      my ($self, $attr) = (@_);      my $self = shift;
793        delete $self->{shift};
     # warnings suck sometimes  
     local $^W = 0;  
   
     # the only reason I bother with all of this is that I experienced  
     # Perl did not always call an object's destructor if you just used  
     # delete.  
     my $t = tied $self->{$attr};  
     if (defined $t and $t =~ m/OnDemand/) {  
         delete $self->{$attr};  
     } else {  
         my $ref = delete $self->{$attr};  
     }  
794  }  }
795    
796    =back
797    
798  =item parse_X ($attribute, { schema option })  =item parse_X ($attribute, { schema option })
799    
800  Parses the schema option field, and returns one or two closures that  Parses the schema option field, and returns one or two closures that
# Line 627  act as a check_X and a destroy_X functio Line 802  act as a check_X and a destroy_X functio
802    
803  This is currently a very ugly hack, parsing the SQL type definition of  This is currently a very ugly hack, parsing the SQL type definition of
804  an object.  But it was bloody handy in my case for hacking this in  an object.  But it was bloody handy in my case for hacking this in
805  quickly.  This is unmanagably unportable across databases.  This  quickly.  This is probably unmanagably unportable across databases;
806  should be replaced by primitives that go the other way, building the  but send me bug reports on it anyway, and I'll try and make the
807  SQL type definition from a more abstract definition of the type.  parsers work for as many databases as possible.
808    
809    This perhaps should be replaced by primitives that go the other way,
810    building the SQL type definition from a more abstract definition of
811    the type.
812    
813  Available functions:  Available functions:
814    
815    parse_string  - parses SQL types of:  =over
816    
817    =item parse_string
818    
819    parses SQL types of:
820    
821    =over
822    
823  =cut  =cut
824    
825    use vars qw($quoted_part $sql_list);
826    
827    $quoted_part = qr/(?: \"([^\"]+)\" | \'([^\']+)\' )/x;
828    $sql_list = qr/\(\s*
829                      (
830                          $quoted_part
831                            (?:\s*,\s* $quoted_part )*
832                      ) \s*\)/x;
833    
834  sub parse_string {  sub parse_string {
835    
836      my ($attribute, $option) = (@_);      my $attribute = shift;
837        my $option = shift;
838    
839      # simple case; return the check_string function.  We don't      # simple case; return the check_string function.  We don't
840      # need a destructor for a string so don't return one.      # need a destructor for a string so don't return one.
# Line 647  sub parse_string { Line 842  sub parse_string {
842          return \&check_string;          return \&check_string;
843      }      }
844    
845  =pod  =item CHAR(N), VARCHAR(N)
846    
847        CHAR(N), VARCHAR(N)  closure checks length of string is less than N characters
           closure checks length of string is less  
           than N characters  
848    
849  =cut  =cut
850    
# Line 662  sub parse_string { Line 855  sub parse_string {
855                  if (length ${$_[0]} > $max_length);                  if (length ${$_[0]} > $max_length);
856          };          };
857    
858  =pod  =item TINYBLOB, BLOB, LONGBLOB
859    
860        TINYBLOB, BLOB, LONGBLOB  checks max. length of string to be 255, 65535 or 16777215 chars
861        TINYTEXT, TEXT, LONGTEXT  respectively.  Also works with "TEXT" instead of "BLOB"
           checks max. length of string to be 255,  
           65535 or 16777215 chars respectively  
862    
863  =cut  =cut
864    
865      } elsif ($option->{sql} =~ m/^\s*(tiny|long|medium)?      } elsif ($option->{sql} =~ m/^\s*(?:tiny|long|medium)?
866                                   (blob|text)/ix) {                                   (?:blob|text)/ix) {
867          my $max_length = ($1 ? ($1 eq "tiny"?255:2**24 - 1)          my $max_length = ($1 ? ($1 eq "tiny"?255:2**24 - 1)
868                            : 2**16 - 1);                            : 2**16 - 1);
869          return sub {          return sub {
870              die "string too long for $attribute"              die "string too long for $attribute"
871                  if (length ${$_[0]} > $max_length);                  if (${$_[0]} and length ${$_[0]} > $max_length);
872          };          };
873    
874  =pod  =item SET("members", "of", "set")
875    
876        SET("members", "of", "set")  checks that the value passed is valid as a SQL set type, and that all
877            checks that the value passed is valid as  of the passed values are allowed to be a member of that set.
           a SQL set type, and that all of the  
           passed values are allowed to be a member  
           of that set  
878    
879  =cut  =cut
880    
881      } elsif ($option->{sql} =~      } elsif (my ($members) = $option->{sql} =~
882               m/^\s*set\s*\(               m/^\s*set\s*$sql_list/oi) {
883                 (\"[^\"]+\" (?:\s*,\s*\"[^\"]+\")* \s* )  
884                 \)\s*$/xi) {          my %members;
885          my $members = $1;          $members{lc($1 || $2)} = 1
886          my ($member, %members);              while ( $members =~ m/\G[,\s]*$quoted_part/cog );
887          while (($member, $members) =  
                ($members =~ m/^[,\s]*\"([^\"]+)\"(.*)$/)) {  
             $members{lc($member)} = 1;  
         }  
888          return sub {          return sub {
889              for my $x (split /,/, ${$_[0]}) {              for my $x (split /\s*,\s*/, ${$_[0]}) {
890                  croak ("SQL set badly formed or invalid member $x "                  croak ("SQL set badly formed or invalid member $x "
891                         ." (SET" . join(",", keys %members). ")")                         ." (SET" . join(",", keys %members). ")")
892                      if (not exists $members{lc($x)});                      if (not exists $members{lc($x)});
893              }              }
894          };          };
895    
896  =pod  =item ENUM("possible", "values")
897    
898        ENUM("possible", "values")  checks that the value passed is one of the allowed values.
           checks that the value passed is one of  
           the allowed values.  
899    
900  =cut  =cut
901    
902      } elsif ($option->{sql} =~      } elsif (my ($values) = $option->{sql} =~
903               m/^\s*enum\s*\(               m/^\s*enum\s*$sql_list/oi ) {
904                 (\"[^\"]+\" (?:\s*,\s*\"[^\"]+\")* \s* )  
905                 \)\s*/xi) {          my %values;
906          my $values = $1;          $values{lc($1 || $2)} = 1
907          my ($value, %values);              while ( $values =~ m/\G[,\s]*$quoted_part/gc);
908          while (($value, $values) =  
                ($values =~ m/^[,\s]*\"([^\"]+)\"(.*)$/)) {  
             $values{lc($value)} = 1;  
         }  
909          return sub {          return sub {
910              croak ("invalid enum value ${$_[0]} must be ("              croak ("invalid enum value ${$_[0]} must be ("
911                     . join(",", keys %values). ")")                     . join(",", keys %values). ")")
912                  if (not exists $values{lc(${$_[0]})});                  if (not exists $values{lc(${$_[0]})});
913          }          }
914    
915    
916      } else {      } else {
917          die ("Please build support for your string SQL type in "          die ("Please build support for your string SQL type in "
918               ."Class::Tangram (".$option->{sql}.")");               ."Class::Tangram (".$option->{sql}.")");
919      }      }
920  }  }
921    
922  # Here is where I map Tangram::Type types to functions.  =back
 # Format:  
 #  type => {  
 #      check => \&check_X  
 #      parse => \&parse_type  
 #      destroy => \&destroy_X  
 #  }  
 #  
 my %defaults =  
     (  
      int         => { check => \&check_int },  
      real        => { check => \&check_real },  
      string      => {             parse => \&parse_string },  
      ref         => { check => \&check_obj,     destroy => \&destroy_ref },  
      array       => { check => \&check_array,   destroy => \&destroy_array },  
      iarray      => { check => \&check_array,   destroy => \&destroy_array },  
      flat_array  => { check => \&check_flat_array },  
      set         => { check => \&check_set,     destroy => \&destroy_set },  
      iset        => { check => \&check_set,     destroy => \&destroy_set },  
      rawdatetime => { check => \&check_rawdatetime },  
      time        => { check => \&check_time },  
      timestamp   => { check => \&check_timestamp },  
      flat_hash   => { check => \&check_flat_hash },  
      hash        => { check => \&check_hash,    destroy => \&destroy_hash },  
      perl_dump   => { check => \&check_nothing }  
     );  
   
 =item 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.  
   
 =cut  
   
 sub import_schema($) {  
     my ($class) = (@_);  
   
     eval {  
         my ($fields, $bases, $abstract);  
         {  
             no strict 'refs';  
             $fields = ${"${class}::schema"}->{fields};  
             $bases = ${"${class}::schema"}->{bases};  
             $abstract = ${"${class}::schema"}->{abstract};  
         }  
   
         my $check_class = { };  
         my $cleaners_class = { };  
         my $init_defaults_class = { };  
         my $types_class = { };  
   
         # if this is an abstract type, do not allow it to be  
         # instantiated  
         if ($abstract) {  
             $abstract{$class} = 1;  
         }  
   
         # If there are any base classes, import them first so that the  
         # check, cleaners and init_defaults can be inherited  
         if (defined $bases) {  
             (ref $bases eq "ARRAY")  
                 or die "bases not an array ref for $class";  
   
             # Note that the order of your bases is significant, that  
             # is if you are using multiple iheritance then the later  
             # classes override the earlier ones.  
             #for my $super ( Class::ISA::super_path($class) ) {  
             for my $super ( @$bases ) {  
                 import_schema $super unless (exists $check{$super});  
   
                 # copy each of the per-class configuration hashes to  
                 # this class as defaults.  
                 my ($k, $v);  
                 $types_class->{$k} = $v  
                     while (($k, $v) = each %{ $types{$super} } );  
                 $check_class->{$k} = $v  
                     while (($k, $v) = each %{ $check{$super} } );  
                 $cleaners_class->{$k} = $v  
                     while (($k, $v) = each %{ $cleaners{$super} } );  
                 $init_defaults_class->{$k} = $v  
                     while (($k, $v) = each %{ $init_defaults{$super} } );  
             }  
         }  
   
         # iterate over each of the *types* of fields (string, int, ref, etc.)  
         while (my ($type, $v) = each %$fields) {  
             if (ref $v eq "ARRAY") {  
                 $v = { map { $_, undef } @$v };  
             }  
             my $def = $defaults{$type};  
   
             # iterate each of the *attributes* of a particular type  
             while (my ($attribute, $option) = each %$v) {  
                 $types_class->{$attribute} = $type;  
   
                 # ----- check_X functions ----  
                 if (ref $option eq "HASH" and $option->{check_func}) {  
                     # user-supplied check_X function  
                     $check_class->{$attribute} =  
                         $option->{check_func};  
   
                 } else {  
                     if (not defined $def) {  
                         die "No default check function for type $type";  
                     }  
   
                     # check for a type-specific option hash parser  
                     if ($def->{parse}) {  
                         my ($check, $destroy) =  
                             $def->{parse}->($attribute, $option);  
   
                         $check_class->{$attribute} = $check;  
                         $cleaners_class->{$attribute} = $destroy  
                             if (defined $destroy);  
   
                     } else {  
                         # use the default for this type  
                         $check_class->{$attribute} = $def->{check};  
                     }  
                 }  
923    
924                  # ----- destroy_X functions  =back
                 if (ref $option eq "HASH" and $option->{destroy_func}) {  
                     # user-supplied destroy_X function  
                     $cleaners_class->{$attribute} =  
                         $option->{destroy_func};  
                 } else {  
                     if ($def->{destroy}) {  
                         # use the default for this type  
                         $cleaners_class->{$attribute} =  
                             $def->{destroy};  
                     }  
                 }  
925    
926                  # ----- init_default functions  =back
                 # create empty Set::Object containers as necessary  
                 if ($type =~ m/^i?set$/) {  
                     $init_defaults_class->{$attribute} =  
                         sub { Set::Object->new() };  
                 }  
                 if (ref $option eq "HASH" and $option->{init_default}) {  
                     $init_defaults_class->{$attribute} =  
                         $option->{init_default};  
                 }  
             }  
         }  
         $types{$class} = $types_class;  
         $check{$class} = $check_class;  
         $cleaners{$class} = $cleaners_class;  
         $init_defaults{$class} = $init_defaults_class;  
     };  
927    
928      $@ && die "$@ while trying to import schema for $class";  =head2 Quick Object Dumping and Destruction
 }  
929    
930    =over
931    
932  =item $instance->quickdump  =item $instance->quickdump
933    
# Line 921  currently escape unprintable characters. Line 942  currently escape unprintable characters.
942  =cut  =cut
943    
944  sub quickdump($) {  sub quickdump($) {
945      my ($self) = (@_);      my $self = shift;
946    
947      my $r = "REF ". (ref $self). "\n";      my $r = "REF ". (ref $self). "\n";
948      for my $k (sort keys %$self) {      for my $k (sort keys %$self) {
# Line 949  $k) Line 970  $k)
970  =cut  =cut
971    
972  sub DESTROY($) {  sub DESTROY($) {
973      my ($self) = (@_);      my $self = shift;
974    
975      my $class = ref $self;      my $class = ref $self;
976    
# Line 987  Tangram::Storage->unload method. Line 1008  Tangram::Storage->unload method.
1008  =cut  =cut
1009    
1010  sub clear_refs($) {  sub clear_refs($) {
1011      my ($self) = (@_);      my $self = shift;
   
1012      my $class = ref $self;      my $class = ref $self;
1013    
1014      exists $cleaners{$class} or import_schema($class);      exists $cleaners{$class} or import_schema($class);
# Line 1002  sub clear_refs($) { Line 1022  sub clear_refs($) {
1022      $self->{_NOREFS} = 1;      $self->{_NOREFS} = 1;
1023  }  }
1024    
1025    =back
1026    
1027    =head1 FUNCTIONS
1028    
1029    The following functions are not intended to be called as object
1030    methods.
1031    
1032    =head2 Schema Import
1033    
1034     our $fields = { int => [ qw(foo bar) ],
1035                     string => [ qw(baz quux) ] };
1036    
1037     # Version 1.115 and below compatibility:
1038     our $schema = {
1039        fields => { int => [ qw(foo bar) ],
1040                    string => [ qw(baz quux) ] }
1041        };
1042    
1043    =over
1044    
1045    =item Class::Tangram::import_schema($class)
1046    
1047    Parses a tangram object field list, in C<${"${class}::fields"}> (or
1048    C<${"${class}::schema"}-E<gt>{fields}> to the internal type information
1049    hashes.  It will also define all of the attribute accessor and update
1050    methods in the C<$class> package.
1051    
1052    Note that calling this function twice for the same class is not
1053    tested and may produce arbitrary results.  Patches welcome.
1054    
1055    =cut
1056    
1057    sub import_schema($) {    # Damn this function is long
1058        my $class = shift;
1059    
1060        eval {
1061            my ($fields, $bases, $abstract);
1062            {
1063    
1064                # Here, we go hunting around for their defined schema and
1065                # options
1066                no strict 'refs';
1067                local $^W=0;
1068                eval {
1069                    $fields = (${"${class}::fields"} ||
1070                               ${"${class}::schema"}->{fields});
1071                    $abstract = (${"${class}::abstract"} ||
1072                                 ${"${class}::schema"}->{abstract});
1073                    $bases = ${"${class}::schema"}->{bases};
1074                };
1075                if ( my @stack = @{"${class}::ISA"}) {
1076                    # clean "bases" information from @ISA
1077                    my %seen = map { $_ => 1 } $class, __PACKAGE__;
1078                    $bases = [];
1079                    while ( my $super = pop @stack ) {
1080                        if ( defined ${"${super}::schema"}
1081                             or defined ${"${super}::fields"} ) {
1082                            push @$bases, $super;
1083                        } else {
1084                            push @stack, grep { !$seen{$_}++ }
1085                                @{"${super}::ISA"};
1086                        }
1087                    }
1088                    if ( !$fields and !@$bases ) {
1089                        die ("No schema and no Class::Tangram "
1090                             ."superclass for $class; define "
1091                             ."${class}::fields!");
1092                    }
1093                }
1094            }
1095    
1096            # if this is an abstract type, do not allow it to be
1097            # instantiated
1098            if ($abstract) {
1099                $abstract{$class} = 1;
1100            }
1101    
1102            # If there are any base classes, import them first so that the
1103            # check, cleaners and init_defaults can be inherited
1104            if (defined $bases) {
1105                (ref $bases eq "ARRAY")
1106                    or die "bases not an array ref for $class";
1107    
1108                # Note that the order of your bases is significant, that
1109                # is if you are using multiple iheritance then the later
1110                # classes override the earlier ones.
1111                for my $super ( @$bases ) {
1112                    import_schema $super unless (exists $check{$super});
1113    
1114                    # copy each of the per-class configuration hashes to
1115                    # this class as defaults.
1116                    my ($k, $v);
1117    
1118                    # FIXME - this repetition of code is getting silly :)
1119                    $types{$class}->{$k} = $v
1120                        while (($k, $v) = each %{ $types{$super} } );
1121                    $check{$class}->{$k} = $v
1122                        while (($k, $v) = each %{ $check{$super} } );
1123                    $cleaners{$class}->{$k} = $v
1124                        while (($k, $v) = each %{ $cleaners{$super} } );
1125                    $attribute_options{$class}->{$k} = $v
1126                        while (($k, $v) = each %{ $attribute_options{$super} } );
1127                    $init_defaults{$class}->{$k} = $v
1128                        while (($k, $v) = each %{ $init_defaults{$super} } );
1129                    $required_attributes{$class}->{$k} = $v
1130                        while (($k, $v) = each %{ $required_attributes{$super} } );
1131                }
1132            }
1133    
1134            # iterate over each of the *types* of fields (string, int, ref, etc.)
1135            while (my ($type, $v) = each %$fields) {
1136                if (ref $v eq "ARRAY") {
1137                    $v = { map { $_, undef } @$v };
1138                }
1139                my $def = $defaults{$type};
1140    
1141                # iterate each of the *attributes* of a particular type
1142                while (my ($attribute, $options) = each %$v) {
1143    
1144                    # this is what we are finding out about each attribute
1145                    # $type is already set
1146                    my ($default, $check_func, $required, $cleaner);
1147                    # set defaults from what they give
1148                    $options ||= {};
1149                    if (ref $options eq "HASH" or
1150                        UNIVERSAL::isa($options, 'Tangram::Type')) {
1151                        ($check_func, $default, $required, $cleaner)
1152                            = @{$options}{qw(check_func init_default
1153                                             required destroy_func)};
1154                    }
1155    
1156                    # Fill their settings with info from defaults
1157                    if (ref $def eq "HASH") {
1158    
1159                        # try to magically parse their options
1160                        if ( $def->{parse} and !($check_func and $cleaner) ) {
1161                            my @a = $def->{parse}->($attribute, $options);
1162                            $check_func ||= $a[0];
1163                            $cleaner ||= $a[1];
1164                        }
1165    
1166                        # fall back to defaults for this class
1167                        $check_func ||= $def->{check_func};
1168                        $cleaner ||= $def->{destroy_func};
1169                        $default = $def->{init_default} unless defined $default;
1170                    }
1171    
1172                    # everything must be checked!
1173                    die "No check function for ${class}\->$attribute (type $type)"
1174                        unless (ref $check_func eq "CODE");
1175    
1176                    $types{$class}->{$attribute} = $type;
1177                    $check{$class}->{$attribute} = $check_func;
1178                    {
1179                        no strict "refs";
1180                        local ($^W) = 0;
1181    
1182                        # build an appropriate "get_attribute" method, and
1183                        # define other per-type methods
1184                        my ($get_closure, $set_closure);
1185    
1186                        # implement with closures for speed
1187                        if ( $type =~ m/i?set/ ) {
1188    
1189                            # GET_$attribute (Set::Object)
1190                            $get_closure = sub {
1191                                my $self = shift;
1192                                if ( !defined $self->{$attribute} ) {
1193                                    $self->{$attribute} = Set::Object->new();
1194                                }
1195                                my $set = $self->{$attribute};
1196                                ( wantarray ? $set->members : $set )
1197                            };
1198    
1199                            # and add a whole load of other functions too
1200                            for my $set_method (qw(includes insert size clear
1201                                                   remove)) {
1202    
1203                                # ${attribute}_includes, etc
1204                                my $set_method_closure = sub {
1205                                    my $self = shift;
1206                                    $self->{$attribute} = Set::Object->new()
1207                                        unless defined $self->{$attribute};
1208                                    return $self->{$attribute}->$set_method(@_);
1209                                };
1210                                *{$class."::${attribute}_$set_method"} =
1211                                    $set_method_closure unless
1212                                        (defined &{$class."::${attribute}_$set_method"});
1213                            }
1214    
1215                        } elsif ( $type =~ m/i?array/ ) {
1216    
1217                            # GET_$attribute (array)
1218                            # allow array slices, and return whole array
1219                            # in list context
1220                            $get_closure = sub {
1221                                my $array = ($_[0]->{$attribute} ||= []);
1222                                shift;
1223                                if ( @_ ) {
1224                                    @{$array}[@_];
1225                                } else {
1226                                    ( wantarray ? @{ $array } : $array )
1227                                }
1228                            };
1229    
1230                        } elsif ( $type =~ m/i?hash/ ) {
1231                            # GET_$attribute (hash)
1232                            # allow hash slices, and return whole hash in
1233                            # list context
1234                            $get_closure = sub {
1235                                my $hash = ($_[0]->{$attribute} ||= {});
1236                                shift;
1237                                if ( @_ ) {
1238                                    @{$hash}{@_}
1239                                } else {
1240                                    ( wantarray ? %{ $hash } : $hash );
1241                                }
1242                            };
1243                        } else {
1244                            # GET_$attribute (scalar)
1245                            # return value only
1246                            $get_closure = sub { $_[0]->{$attribute}; };
1247                        }
1248    
1249                        *{$class."::get_$attribute"} = $get_closure
1250                            unless (defined &{$class."::get_$attribute"});
1251    
1252                        # SET_$attribute (all)
1253                        my $checkit = \$check{$class}->{$attribute};
1254    
1255                        # required hack for strings - duplicate the code
1256                        # to avoid the following string comparison for
1257                        # every set
1258                        if ( $type eq "string" ) {
1259                            $set_closure = sub {
1260                                my $self = shift;
1261                                my $value = shift;
1262                                eval {
1263                                    if ( defined $value and length $value ) {
1264                                        ${$checkit}->(\$value);
1265                                    } elsif ( $required ) {
1266                                        die "value is required"
1267                                    } elsif ( defined $required ) {
1268                                        die "value must be defined"
1269                                            unless defined $value;
1270                                    }
1271                                };
1272                                $@ && croak("value failed type check - ${class}->"
1273                                            ."set_$attribute('$value') ($@)");
1274                                $self->{$attribute} = $value;
1275                            };
1276                        } else {
1277                            $set_closure = sub {
1278                                my $self = shift;
1279                                my $value = shift;
1280                                eval {
1281                                    if ( $value ) {
1282                                        ${$checkit}->(\$value);
1283                                    } elsif ( $required ) {
1284                                        die "value is required"
1285                                    } elsif ( defined $required ) {
1286                                        die "value must be defined"
1287                                            unless defined $value;
1288                                    }
1289                                };
1290                                $@ && croak("value failed type check - ${class}->"
1291                                        ."set_$attribute('$value') ($@)");
1292                                $self->{$attribute} = $value;
1293                            };
1294                        }
1295    
1296                        # now export them into the caller's namespace
1297                        my ($getter, $setter)
1298                            = ("get_$attribute", "set_$attribute");
1299                        *{$class."::$getter"} = $get_closure
1300                            unless defined &{$class."::$getter"};
1301                        *{$class."::$setter"} = $set_closure
1302                            unless defined &{$class."::$setter"};
1303    
1304                        *{$class."::$attribute"} = sub {
1305                            my $self = shift;
1306                            if ( @_ ) {
1307                                warn("The OO Police say change your call "
1308                                     ."to ->set_$attribute") if ($^W);
1309                                #goto $set_closure;  # NO!  BAD!! :-)
1310                                return $self->$setter(@_);
1311                            } else {
1312                                return $self->$getter(@_);
1313                                #goto $get_closure;
1314                            }
1315                        } unless defined &{$class."::$attribute"};
1316                    }
1317    
1318                    $cleaners{$class}->{$attribute} = $cleaner
1319                        if (defined $cleaner);
1320                    $init_defaults{$class}->{$attribute} = $default
1321                        if (defined $default);
1322                    $required_attributes{$class}->{$attribute} = $required
1323                        if (defined $required);
1324                    $attribute_options{$class}->{$attribute} =
1325                        ( $options || {} );
1326                }
1327            }
1328        };
1329    
1330        $@ && die "$@ while trying to import schema for $class";
1331    }
1332    
1333    =back
1334    
1335    =head2 Run-time type information
1336    
1337    It is possible to access the data structures that Class::Tangram uses
1338    internally to verify attributes, create objects and so on.
1339    
1340    This should be considered a B<HIGHLY EXPERIMENTAL> interface to
1341    B<INTERNALS> of Class::Tangram.
1342    
1343    Class::Tangram keeps seven internal hashes:
1344    
1345    =over
1346    
1347    =item C<%types>
1348    
1349    C<$types{$class}-E<gt>{$attribute}> is the tangram type of each attribute,
1350    ie "ref", "iset", etc.  See L<Tangram::Type>.
1351    
1352    =item C<%attribute_options>
1353    
1354    C<$attribute_options{$class}-E<gt>{$attribute}> is the options hash
1355    for a given attribute.
1356    
1357    =item C<%required_attributes>
1358    
1359    C<$required_attributes{$class}-E<gt>{$attribute}> is the 'required'
1360    option setting for a given attribute.
1361    
1362    =item C<%check>
1363    
1364    C<$check{$class}-E<gt>{$attribute}> is a function that will be passed
1365    a reference to the value to be checked and either throw an exception
1366    (die) or return true.
1367    
1368    =item C<%cleaners>
1369    
1370    C<$attribute_options{$class}-E<gt>{$attribute}> is a reference to a
1371    destructor function for that attribute.  It is called as an object
1372    method on the object being destroyed, and should ensure that any
1373    circular references that this object is involved in get cleared.
1374    
1375    =item C<%abstract>
1376    
1377    C<$abstract-E<gt>{$class}> is set if the class is abstract
1378    
1379    =item C<%init_defaults>
1380    
1381    C<$init_defaults{$class}-E<gt>{$attribute}> represents what an
1382    attribute is set to automatically if it is not specified when an
1383    object is created. If this is a scalar value, the attribute is set to
1384    the value. If it is a function, then that function is called (as a
1385    method) and should return the value to be placed into that attribute.
1386    If it is a hash ref or an array ref, then that structure is COPIED in
1387    to the new object.  If you don't want that, you can do something like
1388    this:
1389    
1390       [...]
1391        flat_hash => {
1392            attribute => {
1393                init_default => sub { { key => "value" } },
1394            },
1395        },
1396       [...]
1397    
1398    Now, every new object will share the same hash for that attribute.
1399    
1400  =back  =back
1401    
1402    There are currently four functions that allow you to access parts of
1403    this information.
1404    
1405    =over
1406    
1407    =item Class::Tangram::attribute_options($class)
1408    
1409    Returns a hash ref to a data structure from attribute names to the
1410    option hash for that attribute.
1411    
1412    =cut
1413    
1414    sub attribute_options($) {
1415        my $class = shift;
1416        return $attribute_options{$class};
1417    }
1418    
1419    =item Class::Tangram::attribute_types($class)
1420    
1421    Returns a hash ref from attribute names to the tangram type for that
1422    attribute.
1423    
1424    =cut
1425    
1426    sub attribute_types($) {
1427        my $class = shift;
1428        return $types{$class};
1429    }
1430    
1431    =item Class::Tangram::required_attributes($class)
1432    
1433    Returns a hash ref from attribute names to the 'required' option setting for
1434    that attribute.  May also be called as a method, as in
1435    C<$instance-E<gt>required_attributes>.
1436    
1437    =cut
1438    
1439    sub required_attributes($) {
1440        my $class = ref $_[0] || $_[0];
1441        return $required_attributes{$class};
1442    }
1443    
1444    =item Class::Tangram::init_defaults($class)
1445    
1446    Returns a hash ref from attribute names to the default intial values for
1447    that attribute.  May also be called as a method, as in
1448    C<$instance-E<gt>init_defaults>.
1449    
1450    =cut
1451    
1452    sub init_defaults($) {
1453        my $class = ref $_[0] || $_[0];
1454        return $init_defaults{$class};
1455    }
1456    
1457    =item Class::Tangram::known_classes
1458    
1459    This function returns a list of all the classes that have had their
1460    object schema imported by Class::Tangram.
1461    
1462    =cut
1463    
1464    sub known_classes {
1465        return keys %types;
1466    }
1467    
1468    =item Class::Tangram::is_abstract($class)
1469    
1470    This function returns true if the supplied class is abstract.
1471    
1472    =cut
1473    
1474    sub is_abstract {
1475        my $class = shift;
1476        $class eq "Class::Tangram" && ($class = shift);
1477    
1478        exists $cleaners{$class} or import_schema($class);
1479    }
1480    
1481    =item Class->set_init_default(attribute => $value);
1482    
1483    Sets the default value on an attribute for newly created "Class"
1484    objects, as if it had been declared with init_default.  Can be called
1485    as a class or an instance method.
1486    
1487    =cut
1488    
1489    sub set_init_default {
1490        my $invocant = shift;
1491        my $class = ref $invocant || $invocant;
1492    
1493        exists $init_defaults{$class} or import_schema($class);
1494    
1495        while ( my ($attribute, $value) = splice @_, 0, 2) {
1496            $init_defaults{$class}->{$attribute} = $value;
1497        }
1498    }
1499    
1500    =back
1501    
1502    =cut
1503    
1504    # a little embedded package
1505    
1506    package Tangram::Transient;
1507    
1508    BEGIN {
1509        eval "use base qw(Tangram::Type)";
1510        if ( $@ ) {
1511            # no tangram
1512        } else {
1513            $Tangram::Schema::TYPES{transient} = bless {}, __PACKAGE__;
1514        }
1515    }
1516    
1517    sub coldefs { }
1518    
1519    sub get_exporter { }
1520    sub get_importer { }
1521    
1522    sub get_import_cols {
1523    #    print "Get_import_cols:" , Dumper \@_;
1524        return ();
1525    }
1526    
1527  =head1 SEE ALSO  =head1 SEE ALSO
1528    
1529  L<Tangram::Schema>  L<Tangram::Schema>
# Line 1013  B<A guided tour of Tangram, by Sound Obj Line 1532  B<A guided tour of Tangram, by Sound Obj
1532    
1533   http://www.soundobjectlogic.com/tangram/guided_tour/fs.html   http://www.soundobjectlogic.com/tangram/guided_tour/fs.html
1534    
1535  =head1 BUGS/TODO  =head1 DEPENDENCIES
1536    
1537    The following modules are required to be installed to use
1538    Class::Tangram:
1539    
1540  More datetime types.  I originally avoided the DMDateTime type because     Set::Object => 1.02
1541  Date::Manip is self-admittedly the most bloated module on CPAN, and I     Pod::Constants => 0.11
1542  didn't want to be seen encouraging it.  Then I found out about     Test::Simple => 0.18
1543  autosplit :-}.     Date::Manip => 5.21
1544    
1545  More AUTOLOAD methods, in particular for container types such as  Test::Simple and Date::Manip are only required to run the test suite.
 array, hash, etc.  
1546    
1547  This documentation should be easy enough for a fool to understand.  If you find Class::Tangram passes the test suite with earlier versions
1548    of the above modules, please send me an e-mail.
1549    
1550    =head2 MODULE RELEASE
1551    
1552    This is Class::Tangram version 1.12.
1553    
1554    =head1 BUGS/TODO
1555    
1556  There should be more functions for breaking loops; in particular, a  There should be more functions for breaking loops; in particular, a
1557  standard function called drop_refs($obj), which replaces references to  standard function called C<drop_refs($obj)>, which replaces references
1558  $obj with the appropriate Tangram::RefOnDemand so that an object can  to $obj with the appropriate C<Tangram::RefOnDemand> object so that an
1559  be unloaded via Tangram::Storage->unload() and actually have a hope of  object can be unloaded via C<Tangram::Storage->unload()> and actually
1560  being reclaimed.  Another function that would be handy would be a  have a hope of being reclaimed.  Another function that would be handy
1561  deep "mark" operation for mark & sweep garbage collection.  would be a deep "mark" operation for manual mark & sweep garbage
1562    collection.
1563    
1564    Need to think about writing some functions using C<Inline> for speed.
1565    One of these days...
1566    
1567    Allow C<init_default> values to be set in a default import function?
1568    
1569    ie
1570    
1571      use MyClassTangramObject -defaults => { foo => "bar" };
1572    
1573  =head1 AUTHOR  =head1 AUTHOR
1574    
1575  Sam Vilain, <sam@vilain.net>  Sam Vilain, <sam@vilain.net>
1576    
1577    =head2 CREDITS
1578    
1579     # Some modifications
1580     # Copyright 2001 Micro Sharp Technologies, Inc., Vancouver, WA, USA
1581     # Author: Karl M. Hegbloom <karlheg@microsharp.com>
1582     # Perl Artistic Licence.
1583    
1584    Many thanks to Charles Owens and David Wheeler for their feedback,
1585    ideas, patches and bug testing.
1586    
1587  =cut  =cut
1588    
1589  69;  69;
1590    
1591    __END__
1592    
1593     # From old SYNOPSIS, I decided it was too long.  A lot of
1594     # the information here needs to be re-integrated into the
1595     # POD.
1596    
1597     package Project;
1598    
1599     # here's where we build the individual object schemas into
1600     # a Tangram::Schema object, which the Tangram::Storage
1601     # class uses to know which tables and columns to find
1602     # objects.
1603     use Tangram::Schema;
1604    
1605     # TIMTOWTDI - this is the condensed manpage version :)
1606     my $dbschema = Tangram::Schema->new
1607         ({ classes =>
1608           [ 'Orange'   => { fields => $Orange::fields },
1609             'MyObject' => { fields => $MyObject::schema }, ]});
1610    
1611     sub schema { $dbschema };
1612    
1613     package main;
1614    
1615     # See Tangram::Relational for instructions on using
1616     # "deploy" to create the database this connects to.  You
1617     # only have to do this if you want to write the objects to
1618     # a database.
1619     use Tangram::Relational;
1620     my ($dsn, $u, $p);
1621     my $storage = Tangram::Relational->connect
1622                       (Project->schema, $dsn, $u, $p);
1623    
1624     # Create an orange
1625     my $orange = Orange->new(
1626                              juiciness => 8,
1627                              type => 'Florida',
1628                              tag => '',  # required
1629                             );
1630    
1631     # Store it
1632     $storage->insert($orange);
1633    
1634     # This is how you get values out of the objects
1635     my $juiciness = $orange->juiciness;
1636    
1637     # a "ref" must be set to a blessed object, any object
1638     my $grower = bless { name => "Joe" }, "Farmer";
1639     $orange->set_grower ($grower);
1640    
1641     # these are all illegal - type checking is fairly strict
1642     my $orange = eval { Orange->new; };         print $@;
1643     eval { $orange->set_juiciness ("Yum"); };   print $@;
1644     eval { $orange->set_segments (31); };       print $@;
1645     eval { $orange->set_grower ("Mr. Nice"); }; print $@;
1646    
1647     # Demonstrate some "required" functionality
1648     eval { $orange->set_type (''); };           print $@;
1649     eval { $orange->set_type (undef); };        print $@;
1650     eval { $orange->set_tag (undef); };         print $@;
1651    
1652     # this works too, but is slower
1653     $orange->get( "juiciness" );
1654     $orange->set( juiciness => 123,
1655                   segments  => 17 );
1656    
1657     # Re-configure init_default - make each new orange have a
1658     # random juiciness
1659     $orange->set_init_default( juiciness => sub { int(rand(45)) } );

Legend:
Removed from v.1.2  
changed lines
  Added in v.1.3

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed