The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

SPOPS::Tie - Simple class implementing tied hash with some goodies

SYNOPSIS

 # Create the tied hash
 use SPOPS::Tie;
 my ( %data );
 my @fields = qw( first_name last_name login birth_date );
 tie %data, 'SPOPS::Tie', $class, \@fields;

 # Store some simple properties
 $data{first_name} = 'Charles';
 $data{last_name}  = 'Barkley';
 $data{login}      = 'cb';
 $data{birth_date} = '1957-01-19';

 # Store a temporary property
 $data{tmp_rebound_avg} = 11.3;

 while ( my ( $prop, $val ) = each %data ) {
   printf( "%-15s: %s\n", $prop, $val );
 }

 # Note that output does not include 'tmp_rebound_avg'
 >first_name     : Charles
 >login          : cb
 >last_name      : Barkley
 >birth_date     : 1957-01-19

 print "Rebounding Average: $data{tmp_rebound_avg}\n";

 # But you can access it still the same
 >Rebounding Average: 11.3

DESCRIPTION

Stores data for a SPOPS object, and also some accompanying materials such as whether the object has been changed and any temporary variables.

Checking Changed State

You can check whether the data have changed since the last fetch by either calling the method of the SPOPS object (recommended) or asking for the '_changed' key from the tied() object:

 # See if this object has changed
 if (tied %data){_changed} ) {;
  ...do stuff...
 } 

 # Tell the object that it has changed (force)
 (tied %data){_changed} = 1;

Note that this state is automatically tracked based on whether you set any property of the object, so you should never need to do this. See SPOPS for more information about the changed methods.

Tracking Temporary Variables

Note that this section only holds true if you have field-checking turned on (by passing an arrayref of fields in the 'field' key of the hashref passed as the second parameter in the tie call).

At times you might wish to keep information with the object that is only temporary and not supposed to be serialized with the object. However, the 'valid property' nature of the tied hash prevents you from storing information in properties with names other than those you pass into the initial call to tie(). What to do?

Have no fear! Simply prefix the property with 'tmp_' (or something else, see below) and SPOPS::Tie will keep the information at the ready for you:

 my ( %data );
 my $class = 'SPOPS::User';
 tie %data, 'SPOPS::Tie', $class, [ qw/ first_name last_name login / ];
 $data{first_name} = 'Chucky';
 $data{last_name}  = 'Gordon';
 $data{login}      = 'chuckg';
 $data{tmp_inoculation} = 'Jan 16, 1981';

For as long as the hash %data is in scope, you can reference the property 'tmp_inoculation'. However, you can only reference it directly. You will not see the property if you iterate through hash using keys or each.

Lazy Loading

You can specify you want your object to be lazy loaded when creating the tie interface:

  my $fields = [ qw/ first_name last_name login life_history / ];
  my $params = { is_lazy_load  => 1,
                 lazy_load_sub => \&load_my_variables,
                 field         => $fields };
  tie %data, 'SPOPS::Tie', $class, $params;

Storing Information for Internal Use

The final kind of information that can be stored in a SPOPS object is 'internal' information. This is similar to temporary variables, but is typically only used in the internal SPOPS mechanisms -- temporary variables are often used to store computed results or other information for display rather than internal use.

For example, the SPOPS::DBI module could allow you to create validating subroutines to ensure that your data conform to some sort of specification:

 push @{ $obj->{_internal_validate} }, \&ensure_consistent_date;

Most of the time you will not need to deal with this, but check the documentation for the object you are using.

Field Mapping

You can setup a mapping of fields to make an SPOPS object look like another SPOPS object even though its storage is completely different. For instance, say we were tying a legacy data management of system of book data to a website. Our web designers do not like to see FLDNMS LK THS since they are used to the more robust capabilities of modern data systems.

So we can use the field mapping capabilities of SPOPS::Tie to make the objects more palatable:

 my $obj = tie %data, 'SPOPS::Tie', 'My::Book',
                      { field_map => { author         => 'AUTH',
                                       title          => 'TTL',
                                       printing       => 'PNUM',
                                       classification => 'CLSF' } };

(See the SPOPS documentation for how to declare this in your SPOPS configuration.)

So your web designers can use the objects:

 print "Book author: $book->{author}\n",
       "Title: $book->{title}\n";

But the data are actually stored in the object (and retrieved by an each query on the object -- be careful) using the old, ugly names 'AUTH', 'TTL', 'PNUM' and 'CLSF'.

This can be extremely helpful not only to rename fields for aesthetic reasons, but also to make objects conform to the same interface.

Multivalue Fields

Some data storage backends -- such as LDAP -- can store multiple values for a single field, and SPOPS::Tie can represent it.

Three basic rules when dealing with multivalue fields:

  1. No duplicate values allowed.

  2. Values are not sorted. If you need sorted values, use the tools perl provides you.

  3. Values are always retrieved from a multivalue field as an array reference.

The interface for setting values is somewhat different, so sit up straight and pay attention.

(0) Telling SPOPS::Tie

 my $obj = tie %data, 'SPOPS::Tie', 'My::LDAP::Person',
                      { multivalue => [ 'objectclass' ] };

This means only the field 'objectclass' will be treated as a multivalue field.

(1) Creating a new object

 my $person = My::LDAP::Person->new();
 $person->{objectclass} = [ 'inetOrgPerson', 'organizationalPerson',
                            'person' ];
 $person->{sn}          = 'Winters';
 $person->{givenname}   = 'Chris';
 $person->{mail}        = 'chris@cwinters.com';
 $person->save;

The property 'objectclass' here is multivalued and currently has three values: 'inetOrgPerson', 'organizationalPerson', and 'person'.

(2) Fetching and displaying an object

 my $person = My::LDAP::Person->fetch( 'chris@cwinters.com' );
 print "Person info: $person->{givenname} $person->{sn} ",
       "(mail: $person->{mail})\n";
 print "Classes: ", join( ', ', @{ $person->{objectclass} } ), "\n";

Displays:

 > Person info: Chris Winters (mail: chris@cwinters.com)
 > Classes: inetOrgPerson, organizationalPerson, person

Note that if there were no values for defined for objectclass, the value retrieval would return an arrayref. Value retrievals always return an array reference, even if there are no values. This is to provide consistency of interface, and so you can always use the value as an array reference without cumbersome checking to see if the value is undef.

(3) Setting a single value

 my $person = My::LDAP::Person->fetch( 'chris@cwinters.com' );
 $person->{objectclass} = 'newSchemaPerson';
 $person->save;

The property 'objectclass' now has four values: 'inetOrgPerson', 'organizationalPerson', 'person', and 'newSchemaPerson'.

(4) Setting all values

 my $person = My::LDAP::Person->fetch( 'chris@cwinters.com' );
 $person->{objectclass} = [ 'newSchemaPerson', 'reallyNewPerson' ];
 $person->save;

The property 'objectclass' now has two values: 'newSchemaPerson', 'reallyNewPerson'.

(5) Removing one value

 my $person = My::LDAP::Person->fetch( 'chris@cwinters.com' );
 $person->{objectclass} = { remove => 'newSchemaPerson' };
 $person->save;

The property 'objectclass' now has one value: 'reallyNewPerson'.

 my $object_class_thingy = $person->{objectclass};
 print "Object class return is a: ", ref $object_class_thingy, "\n";

Displays:

 > Object class return is a: ARRAY

Again: when a multivalued property is retrieved it always returns an arrayref, even if there is only one value.

(6) Modifying one value

 my $person = My::LDAP::Person->fetch( 'chris@cwinters.com' );
 $person->{objectclass} =
      { modify => { reallyNewPerson => 'totallyNewPerson' } };
 $person->save;

The property 'objectclass' still has one value, but it has been changed to: 'totallyNewPerson'.

Note: you could have gotten the same result in this example by doing:

 $person->{objectclass} = [ 'totallyNewPerson' ];
 $person->save;

(7) Removing all values

 my $person = My::LDAP::Person->fetch( 'chris@cwinters.com' );
 $person->{objectclass} = undef;
 $person->save;

The property 'objectclass' now has no values.

You can also get the same result with:

 $person->{objectclass} = [];
 $person->save;

METHODS

See Tie::Hash or perltie for details of what the different methods do.

TO DO

Benchmarking

We should probably benchmark this thing to see what it can do

BUGS

None known.

SEE ALSO

perltie

COPYRIGHT

Copyright (c) 2001-2002 intes.net, inc.. All rights reserved.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

AUTHORS

Chris Winters <chris@cwinters.com>