Revision history for Perl extension SPOPS.
0.59 Mon Mar 18 23:33:06 EST 2002
Overall:
* Fix module versions to eliminate conflicts with CPAN --
everything has been upgraded in CVS to a version 2.0 so we can
start from a known good version and have no danger of
retrograding. Thanks to Merlyn <merlyn@stonehenge.com> for the
friendly notice.
0.58 Thu Mar 14 09:03:35 EST 2002
Overall:
* Modified DBI id_clause() quoting (in both single and multifield keys)
so it will call ->sql_quote() (in SPOPS::SQLInterface or
overridden by the SPOPS DBI driver class) instead of ->quote()
directly on the db handle, since some drivers don't know what to
do with the extra type information. (Found with latest version --
0.11 -- of DBD::SQLite.)
Individual:
* SPOPS/DBI.pm:
- Updated id_clause() to call ->sql_quote(). See 'Overall' for
notes.
* SPOPS/ClassFactory/DBI.pm:
- Updated the generated id_clause() for multifield primary keys
to call ->sql_quote(). See 'Overall' for notes.
0.57 Sat Feb 23 14:02:22 EST 2002
Overall:
* SPOPS now works with Oracle! All tests pass and all the scripts
under eg/ work ok as well, but you might consider this support
beta until someone who actually knows Oracle takes a look.
* SPOPS now works with SQLite! SQLite is an embeddable database,
and DBD::SQLite comes with the database source ready to
compile. You can use it instead of DBD::Sprite or DBD::CSV -- it
is much faster and behaves like a real database. Kudos to the
indefatigable Matt Sergant!
* Fix tests and (hopefully) Makefile.PL to follow all dependencies
properly.
Individual:
* Makefile.PL:
- Add 'Class::Factory' as a dependency
* eg/fetch_all.pl:
- Display all known objects in eg/ modules
* eg/users_groups_oracle.sql, eg/users_groups_clear_oracle.sql:
- Data create/clear scripts for Oracle
* eg/users_groups_sqlite.pl:
- Data create script for SQLite
* eg/My/(User|Group|Doodad|Security).pm:
- Use named sequences versus relying on default behavior
* SPOPS.pm:
- Modified clone() to respect false-but-defined values passed in
for the new object. Thanks to Ray Zimmerman <rz10@cornell.edu>
for the patch.
* SPOPS/DBI.pm:
- Add 'sql_case_insensitive' to allow drivers to specify a
case-insensitive operator. (Default: 'LIKE', which may or may not
work for all databases but at least it will not break anything.)
- Add docs for the pre_fetch_id() and post_fetch_id() callbacks
to get/find ID values on an insert.
- The return value from pre_fetch_id() is by default set to
no_quote -- the implementer of pre_fetch_id() is responsible for
either quoting it or for telling SPOPS::DBI that it is ok to
quote it using the normal means.
* SPOPS/DBI/Pg.pm:
- Specify the '~*' case-insensitive operator
- Modify how we fetch/retrieve sequence values.
* SPOPS/DBI/Oracle.pm:
- Hey, we support Oracle now! (See above)
* SPOPS/DBI/SQLite.pm:
- Hey, we support SQLite now! (See above)
* SPOPS/Exception.pm:
- You can now pass as many arguments as you want to throw() --
they will all get concatenated into the exception message unless
the last one is a hashref, which will be set to the optional
parameter hashref.
* SPOPS/Import.pm, SPOPS/Export.pm:
- Modify to use Class::Factory
* SPOPS/Import/TableTransform/(Oracle|SQLite).pm:
- Add table transforms for Oracle and SQLite
* SPOPS/Key/(Random|UUID).pm:
- Quote the return value from pre_fetch_id()
* SPOPS/Secure.pm:
- Add a 'require SPOPS::Exception::Security' to bring in the
module, but add it after all the constants have been defined so
we don't get into a deadlock
* t/05_exception.t:
- Modify stringification comparison to work properly all the time
* t/30_dbi.t, t/31_dbi_multifield.t, t/40_ldap.t:
- Only run the END {} if we're running the tests
* t/dbi_config.pl:
- Add Oracle-specific info
0.56 Tue Jan 15 07:17:02 EST 2002
Overall:
* Overhauled the error reporting scheme to use exceptions instead
of package variables; as a result, modified all files that used
'die' to indicate error to throw an exception object
instead. (Backwards compatibility is maintained by echoing the
error information to SPOPS::Error as before.)
* Added examples for specifying DBI/LDAP connection information in
an object configuration, useful for one-off scripts
Individual:
* Makefile.PL:
- Check to see whether Data::UUID is installed. (Thanks to brian
moseley <bcm@maz.org> for pointing out the test failure.)
- Add Devel::StackTrace as a dependency, as well as the
Class::Accessor dependencies
* eg/datasource_configure.pl:
- Added example of specifying DBI configuration in object
configuration
* eg/datasource_configure_ldap.pl:
- Added example of specifying LDAP configuration in object
configuration
* eg/My/DBI/DatasourceConfigure.pm:
- Added sample behavior to create a 'global_datasource_handle()'
for a class to return a DBI handle based on the configuration
information in the object.
* eg/My/LDAP/DatasourceConfigure.pm:
- Added sample behavior to create a 'global_datasource_handle()'
for a class to return a LDAP handle based on the configuration
information in the object.
* SPOPS/DBI.pm:
- Fixed fetch() to deal with: fetch( 0 ) properly (use:
'if defined $id' rather than 'if $id') and remove the default set
of $id to 0 as well (SF Bug: #500514)
* SPOPS/Exception.pm:
- Added base class for exceptions in SPOPS (to replace
SPOPS::Error with backwards compatibility)
* SPOPS/Exception/DBI.pm:
- Create exception class for DBI-specific errors
* SPOPS/Exception/LDAP.pm:
- Create exception class for LDAP-specific errors
* SPOPS/Exception/Security.pm:
- Create exception class for security errors
* SPOPS/Iterator/DBI.pm:
- Ensure we can handle ID field intialization properly, add
helpful debugging messages and get rid of redundant offset check
* SPOPS/Iterator/LDAP.pm:
- Add helpful debugging messages and get rid of redundant offset
check
* SPOPS/Manual/Exceptions.pod:
- Add manual section on exceptions.
* t/03_uuid_key.t:
- Check the build configuration to see if the test should run
* t/05_exception.t:
- Added tests for exceptions
0.55 Mon Dec 31 00:49:15 EST 2001
Overall:
- Added data import and export capabilities to SPOPS
- You can now use a UUID (Universally Unique Identifier) as a key
Individual:
* eg/export_doodads.pl:
- Added sample for SPOPS::Export::Object
* eg/import_doodads.pl:
- Added sample for SPOPS::Import::Object
* SPOPS/DBI/Pg.pm:
- Fixed item to allow specification of the sequence name in
object configuration
* SPOPS/Export.pm:
- Added factory class and parent to exporting routines
* SPOPS/Export/Object.pm:
- Added simple class to take existing data and export it to a
portable format.
* SPOPS/Export/Perl.pm
- Added simple class to export SPOPS objects to a raw perl data
structure.
* SPOPS/Export/SQL.pm
- Added simplistic class to export SPOPS objects to a series of
SQL INSERT statements.
* SPOPS/Export/XML.pm:
- Added simplistic class to export SPOPS objects to XML format
* SPOPS/Import.pm:
- Added factory class and parent to importing routines
* SPOPS/Import/DBI/Table.pm:
- Added class for importing/translating DBI tables. This should
allow you to create a database-neutral schema and have SPOPS
translate it dynamically.
* SPOPS/Import/DBI/TableTransform.pm:
- Added parent factory class for creating transformation objects
* SPOPS/Import/DBI/TableTransform/MySQL.pm:
- Added transformer for generic tables to MySQL
* SPOPS/Import/DBI/TableTransform/Pg.pm:
- Added transformer for generic tables to PostgreSQL
* SPOPS/Import/DBI/TableTransform/Sybase.pm:
- Added transformer for generic tables to Sybase (ASE, ASA and MS
SQL)
* SPOPS/Import/Object.pm:
- Added a simple class to bring into an SPOPS datastore data
formatted by something like SPOPS::Export::Object.
* SPOPS/Key/Random.pm:
- Wrapped the call to $class->CONFIG in an eval {} in case we're not
calling this from an SPOPS class/object
* SPOPS/Key/UUID.pm:
- Created new method for generating keys. The Universally Unique
Identifier is a fairly long string whose uniqueness is (more or
less) guaranteed -- the docs for Data::UUID say that it's good
for millions of keys per second, which is good enough for me :-)
(Note: this apprently does not work on Win32 systems; pointers
are welcome for either a Perl module or an external program that
can do this.)
* SPOPS/Loopback.pm:
- Created a dummy SPOPS class useful for testing. It runs all the
pre/post actions, so you can test out your rules on it as well.
* SPOPS/Manual/ImportExport.pod
- Added manual section for importing and exporting data.
* t/03_uuid_key.t:
- Created test for SPOPS::Key::UUID
* t/04_random_key.t:
- Created test for SPOPS::Key::Random
0.54 Tue Nov 27 07:42:59 EST 2001
Overall:
- Fix bug introduced by 0.53 in the clone() method. (Thanks to Ray
Zimmerman for spotting.)
Individual:
* SPOPS.pm:
- Modify clone() method to behave as previous -- the
multifield-specific behavior is now in a separate method
* SPOPS/ClassFactory/DBI.pm:
- Generate clone() method for classes using multifield primary
keys
* t/31_dbi_multifield.t:
- Modify clone save() test (#12) to specify one value of the ID in
the clone() call and set the other one afterward.
0.53 Sat Nov 24 20:50:32 EST 2001
Overall:
- SPOPS::DBI now supports multiple-field primary keys
- Small bugfixes
Individual:
* eg/My/ReadOnly.pm:
- Example of how you can create read-only objects in SPOPS.
* eg/My/UTFConvert.pm:
- Example of converting to/from unicode values for datasources
that store unicode (WARNING: This is a demo only, and only works
for those 5.6.0 and earlier versions of perl that support 'use
utf8')
* SPOPS.pm:
- Modified how the ID value gets set in initialize() if { id =>
'blah' } is passed in -- this modification enables multifield
keys to work properly.
* SPOPS/ClassFactory/DBI.pm:
- Add id_clause() and id() methods to classes using
multiple-field primary keys (see below).
* SPOPS/DBI.pm:
- Fixed the function that gathered up fields to select to ignore
putting the table name in front of a field unless it has the
'table.' in front, not just 'table'
- Enable multi-field primary keys. All you need to do is specify
multiple fields for your id_field:
Single field:
id_field => 'object_id',
Multiple field:
id_field => [ 'creation_time', 'created_by' ]
SPOPS::DBI will take care of the rest for you. To retrieve a
multi-field record, you just need to specify the ID values
stringified:
# '1005082576' is the creation_time, '45' is the ID of the
# user who created it
my $obj = My::Object->fetch( '1005082576,45' );
* SPOPS/HashFile.pm:
- Always make a backup before doing anything, and keep it around
rather than deleting it.
* t/dbi_config.pl:
- Moved certain information from t/30_dbi.t here and generalized
for other DBI tests.
* t/30_dbi.t:
- Modified to use dbi_config.pl for certain behaviors
* t/31_dbi_multifield.t:
- Added tests for multi-field primary keys.
0.52 Thu Oct 25 23:19:43 EDT 2001
Overall:
- You can now specify defaults for objects so when you instantiate
one via new() the values get automatically filled in. There are
different ways to set these defaults -- see
SPOPS::Manual::Configuration and SPOPS::Manual::CodeGeneration for
examples and ideas.
- Enhancements to SPOPS::LDAP::Multidatasource
- Bugfixes, including one in SPOPS::ClassFactory::DefaultBehavior
that broke old apps using rules
Individual:
* eg/find_defaults_demo.pl:
- Self-contained script used to demonstrate how
My::DBI::FindDefaults works.
* eg/ldap_multidatasource.pl:
- Added script to demonstrate (along with eg/My/LDAPConnect.pm)
how multiple datasources work with SPOPS::LDAP::MultiDatasource
* eg/My/DBI/FindDefaults.pm:
- Example of a SPOPS::ClassFactory behavior that retrieves the
information from a particular record to use as defaults.
* eg/My/LDAPConnect.pm:
- Example of an LDAP connection manager that works with multiple
datasources.
* SPOPS.pm
- Enable default values to be set via the object configuration
(or dynamically, via ClassFactory) that are used to fill up an
object created with new(). You can also use the key 'NOW' to
specify the current date/time or a class and method to deal with
more complicated tasks.
(Accompanying this, modified all implementations -- DBI, LDAP and
GDBM -- plus the iterators -- DBI and GDBM -- to pass a
'skip_default_values => 1' when creating a new object to be
filled with a fetch() or fetch_group().)
* SPOPS/DBI/MySQL.pm:
- Added SPOPS::ClassFactory behavior so we can tell MySQL to find
defaults for fields.
* SPOPS/ClassFactory/DefaultBehavior.pm:
- Bugfix: rulesets used to be generated using the method
'ruleset_add'. We now use 'ruleset_factory' since it's more
descriptive and consistent with the behavior generation
code. However, we forgot to keep the old method enabled for a few
versions. Fixed. (Thanks to Peter Beardsley
<pbeardsley@appropriatesolutions.com> for the report.)
* SPOPS/LDAP/MultiDatasource.pm:
- Make datasources sticky -- each object should be able to find
out where it came from (modified fetch() and fetch_group_all()
with this).
- Override save() and remove() so that we first grab the right
handle (from the datasource put into the object) and pass it along
to the method that does the real work.
- Create fetch_by_dn() so you can actually store in a group the
DNs for objects that reside on multiple servers. (Cool!) Thanks to
Andreas Nolte <andreas.nolte@bertelsmann.de> for testing this out.
0.51 Sun Oct 14 20:23:59 EDT 2001
Overall:
- Centralized SPOPS documentation started
- LDAP bugfixes and additional tests
- Other fixes and goodies
Individual:
* SPOPS/Manual*:
- Added first draft of SPOPS manual. Note that much of the
documentation was removed from other classes, reworked and placed
here. For example, all discussion of configuration keys is now in
one place, SPOPS::Manual::Configuration. Some pieces of the
manual are fairly complete while others are barren. To start,
just do 'perldoc SPOPS::Manual'.
* eg/(items from 0.50):
- Actually added these to the distribution (doh!)
* eg/My/DiscoverField.pm:
- Added sample ClassFactory behavior for auto-discovering fields
in a DBI datasource and using all of them as object properties.
* SPOPS/ClassFactory.pm:
- Allow rules-only classes to be specified in the configuration
field 'rules_from' in addition to in the 'isa' (used for
ClassFactory behaviors).
- change require_isa() to require_config_classes(), and enable it
to require the 'rules_from' classes as well
* SPOPS/ClassFactory/DBI.pm:
- Change 'warn' calls when we get an error to 'SPOPS::_w' calls,
to fit with everything else
- Change generated 'links_to' methods so that we get the database
handle from the correct class. (This hasn't been a problem for
people because most people use the same datasource to hold
related objects. But SPOPS doesn't care!)
* SPOPS/ClassFactory/DefaultBehavior.pm:
- Allow rules-only classes to be specified in the configuration
field 'rules_from' rather than in the 'isa' (used for rulesets)
* SPOPS/ClassFactory/LDAP.pm:
- Add %alias%_add() and %alias%_remove() calls to the 'has_a'
methods.
- Move certain parameter-parsing and object-getting functionality
from separate generated methods and into a method that gets
installed via the 'read_code' slot in the ClassFactory process.
* SPOPS/Iterator/(DBI|LDAP).pm:
- Fixed off-by-one error when using 'limit'
* SPOPS/Iterator/WrapList.pm:
- Added simple implementation of SPOPS::Iterator to wrap around an
already-existing list of objects.
* SPOPS/LDAP.pm:
- Modified the save procedures to first fetch the existing object
then implement a 'replace' on only the items that SPOPS knows
about.
- Modified 'fetch()' to work properly with a secured object that
is fetched with a filter rather than an ID.
- 'fetch_by_dn()' is much more sensible about how it works (it
now does a search for all objects below the particular DN and
returns the only one that should be there)
- Pulled out pre- and post- actions from fetch() so that other
methods can use them.
- You can force a filter to not be used by passing a true value
for the 'no_filter' parameter key to 'fetch'
- ensure that _perform_postfetch() looks at the {skip_security}
setting before checking security on a 'delay_security_check'
(Thanks to Andreas Nolte <Andreas.Nolte@Bertelsmann.de>)
* SPOPS/Secure.pm:
- Move security error reporting (putting error info into
SPOPS::Error plus the 'die' call) into a separate method so it
can be called from elsewhere
* t/40_ldap.t:
- Add tests for new feature signified by ldap_update_only_changed
configuration flag
- Add tests for 'has_a' and 'links_to' linking functionality
0.50 Tue Aug 28 16:59:39 EDT 2001
Overall:
- You can now use LDAP to store your objects! Also included is
SPOPS::LDAP::MultiDatastore, which lets you specify multiple
directories to query for objects. Thanks to MSN Marketing Service
Nordwest, GmbH, for funding development!
- Configuration process has been totally rewritten and renamed
(SPOPS::ClassFactory)
- Added multivalue and name mapping functions tie interface
Individual:
* Makefile.PL:
- Added support for non-interactive execution
('perl Makefile.PL AUTO' if you have a 'spops_test.conf' file
around from a previous test)
- Added support for LDAP test definitions
* eg/README:
- Information about the examples now here
* eg/stock_user_group.pl:
- Added script to stock the sample user/group/security objects.
* eg/users_groups_(mysql|psql|tsql).sql
- Added SQL structure files for the sample user/group/security
objects.
* eg/My/(Common|Group|Security|User).pm:
- Define sample user/group/security objects.
* SPOPS.pm:
- Added (and documented) a few keys to the information returned
by the 'object_description' method.
- Documented information that is used in object configuration for
the tie interface by the 'new()' method.
- Modified 'initialize()' to set object values passed to new()
- Removed 'global_config()' method and the need for it -- it was
only being used for caching and when we tackle that we'll figure
out what's necessary.
- Moved a number of cache methods from SPOPS/DBI.pm here
- Removed the '_idx_data()' method
- The 'clone()' method can now clone an object from one class to
another given the parameter '_class'. This could be an extremely
easy way to copy objects having the same interface from one
datastore to another. (See POD for an example.)
- Moved the method '_determine_limits()' from SPOPS/DBI.pm here,
renaming it 'fetch_determine_limit()'
- Added 'behavior_factory' which installs the default behaviors
(found in SPOPS::ClassFactory::DefaultBehavior) to subclasses
- Added 'as_html' to represent an object as HTML. (Note that the
default just wraps 'as_string' in '<pre>' tags. U-G-L-Y. Override
if you value aesthetics.)
* SPOPS/ClassFactory.pm:
- Added new configuration process. Instead of a monolithic
process this class simply gathers behaviors for a class based on
its inheritance tree. These behaviors are put into a particular
slot (type) and run in turn. This is quite extensible and easy to
customize. See POD for more.
* SPOPS/ClassFactory/DefaultBehavior.pm:
- Holds default behaviors run (potentially) by every SPOPS subclass
* SPOPS/ClassFactory/DBI.pm:
- Holds implementation-specific behaviors for subclasses of
SPOPS::DBI
* SPOPS/ClassFactory/LDAP.pm:
- Holds implementation-specific behaviors for subclasses for
SPOPS::LDAP
* SPOPS/DBI.pm:
- Moved 'check_action_security()' to SPOPS::Secure
- Removed 'initialize()' (now inherits from SPOPS.pm); caching
methods (moved to SPOPS.pm)
- Moved '_determine_limits()' to SPOPS.pm as
'fetch_determine_limit()'
- Added 'behavior_factory' which installs the
implementation-specific behaviors (found in
SPOPS::ClassFactory::DBI) to subclasses
* SPOPS/Initialize.pm:
- Better documented the process() method and added the ability to
pass multiple already-formed SPOPS configuration hashrefs at
once.
- Modified to work with SPOPS::ClassFactory
* SPOPS/Iterator/LDAP.pm:
- Added implementation of SPOPS::Iterator for SPOPS::LDAP
* SPOPS/LDAP.pm:
- Added new datastore for SPOPS. You can now save, fetch and
remove objects from an LDAP datastore as you would a DBI
datastore.
* SPOPS/LDAP/MultiDatastore.pm:
- Add subclass to SPOPS::LDAP - be able to query multiple
directories at once by using this (instead of SPOPS::LDAP) as a
parent class and specifying multiple datasources in the object
configuration.
* SPOPS/Secure.pm
- Updated docs and added information about subclassing to create
custom security.
- Added method 'check_action_security()' from SPOPS/DBI.pm so
that other implementation classes can use it and
non-SPOPS::Secure objects can just call the stub in SPOPS.pm.
* SPOPS/SQLInterface.pm
- Patch from Ray Zimmerman to actually return data when you
specify a return value of 'hash' for db_select().
- Patch from Ray Zimmerman to return from 'db_insert()',
'db_update()' and 'db_delete()' the return value from the
'$sth->execute()' statement. This is an API change but a very
small one -- if successful the return value should still be true,
since even if (for instance) an update affects no rows DBI
returns '0E0' which is numerically zero but evaluates to
true. Also added note regarding various return values to
'db_update()' docs.
* SPOPS/Tie.pm
- Added functionality for multivalued properties. A single
property can have non-duplicate, unordered multiple values. This
is necessary for SPOPS::LDAP. (Documentation included!)
- Added functionality for field mapping. This allows you to
retrofit an existing object interface to a table scheme with
different field names. (Documentation included!)
* t/*
- Renamed all tests from 'name.t' to '##_name.t' so we can have
some control over the order in which they're executed.
- Added LDAP test suite (40_ldap.t).
0.41 Fri Jul 20 07:00:38 EDT 2001
Overall:
- Added iterators to scroll sensibly through resultsets
- Handful of bugfixes and changes.
- Removed the previously deprecated SPOPS::DBI::RandomCode; use
SPOPS::Key::Random instead
- Added more tests and new testing scheme
API CHANGES:
- The return value of 'save()' is now the object itself rather
than the object's ID. This allows you to perform chained method
calls like:
$obj->save()->perform_other_method();
Or even:
$film->add_director(
Director->new({ name => 'Peter Jackson' })->save );
* SPOPS.pm:
- Document special lazy loading column group '_id_field'
- Added 'is_saved()' which returns true if the object has been
saved. (Old method 'saved()' is still there but may be removed at
a later date.)
- Moved 'id()' method to be in the auto-generated class.
- Moved all credits to this file.
* SPOPS/Configure.pm:
- Ray Zimmerman <rz10@cornell.edu> caught a little bug in
'find_config_class()' that might have prevented
'SPOPS::Configure::DBI' from getting invoked properly, which
would have almost certainly led to mass hysteria.
- Ray Zimmerman also caught a typo in the hasa processing.
- Cleaned up docs.
- Moved 'id()' method from SPOPS.pm so a subclass won't get a
superclass's ID.
* SPOPS/Configure/DBI.pm:
- Cleaned up docs.
* SPOPS/Configure/Ruleset.pm:
- Cleaned up docs.
* SPOPS/DBI.pm:
- Implement special lazy loading column group '_id_field'
- Added 'fetch_iterator()' method which sets up the information
for the iterator (runs the query and gets the statement handle)
and returns it.
- Fixed wrong call in 'remove()' that was supposed to check
whether the object had been saved. (Thanks to Ray Zimmerman.)
* SPOPS/Iterator.pm:
- Added new class for an Iterator, or an object that allows you
to step through a resultset one at a time rather than forcing you
to retrieve the entire thing into memory at once. This class
defines the interface as well as the state variables and
methods. Implementing classes only need to implement three
methods to make it work. See docs (which are pretty complete) for
more info.
* SPOPS/Iterator/DBI.pm:
- Added SPOPS::DBI implementation of iterator.
* SPOPS/SQLInterface.pm:
- Allow 'from' to be a scalar in 'db_select()'
- Add 'group' -- mapping to 'GROUP BY' clause -- as a parameter
for 'db_select()'
0.40 Sun Jun 10 14:50:20 EDT 2001
Overall:
* Modified how debugging works (again). This should be a
performance gain -- thanks to posts and pointers on a recent
thread on the mod_perl mailing list (April 2001, "Cutting down on
the DEBUG bloat...")
* Initialization is easier: SPOPS::Initialize should take care of
the details for you. (Thanks to David Boone <dave@bis.bc.ca> for
asking about this.)
* PostgreSQL now supported, as are sequences as a key generation
tool which should open the door for Oracle support.
* Lazy loading of objects now supported. You can specify a logical
group of properties and ask that only those be fetched. Your
database will thank you, as will your memory in a persistent
environment. See 'LAZY LOADING' in SPOPS.pm for details. (Please
note that this is a quite new feature and if you have any
problems and/or feedback, share!) (Tip 'o the hat to Class::DBI
and Master Schwern for inspiration.)
* Note: To use strict field checking, you need to set
'strict_field' in your object configuration to a true value.
Specifics:
* All modules: (hopefully) fixed version numbering CPAN issues.
* SPOPS.pm:
- Implemented (and documented!) the lazy-loading interface.
- Better documented rulesets (thanks to Ray Zimmerman for
prodding).
* SPOPS/Configure.pm
- The primary method in this class ('process_config()' now uses
whatever configuration subclass is appropriate. For instance, if
an SPOPS object has 'SPOPS::DBI' as a parent it will use
'SPOPS::Configure::DBI'. Generally this just ensures that extra
capabilities of the configuration subclass get used (like
'create_relationships()') but it's open-ended enough to allow
interesting things...
- Added some inline and POD documentation to clarify matters.
* SPOPS/Configure/DBI.pm: Thanks to Ray Zimmerman
<rz10@cornell.edu> for spotting an error in the generated
'_remove()' subroutine -- it wasn't passing the parameters
(namely a database handle) through to the 'id_clause()' method
call.
* SPOPS/Configure/Ruleset.pm:
- Fixed error so that an inheritance diamond won't load the same
rule into the ruleset twice, spotted by Ray Zimmerman
<rz10@cornell.edu>.
* SPOPS/DBI.pm
- Modified fetch_group() to accommodate a 'limit' parameter,
which allows limit the number of returned objects as well as
slice that number offset from the beginning. Patch by Rusty
Foster <rusty@kuro5hin.org>.
- Also (inspired by Rusty) modified fetch_group() to retrieve
entire records rather than just the ID field and submitting that
to a fetch() call -- security is still checked just like before,
but since we refactored that action into its own routine we can
call it from both 'fetch()' and 'fetch_group()'. This should
immediately reduce the number of database calls.
- Added (and documented) a new method fetch_count() which
executes a query and counts up the number of records it would
have fetched. This **includes** security restrictions. (Otherwise
it would be misleading...)
- Refactored a number of actions from fetch() and fetch_group()
into separate routines, including _fetch_select_fields() which
returns the raw fields plus the formatted fields.
- Added lazy-loading routines meeting the SPOPS interface.
- fetch_group() will now only prepend the table name to columns
that aren't formatting select statements (e.g., not to
"DATE_FORMAT( fieldname, '%a...' )"
- added docs regarding database handles -- how to create a
'global_db_handle()' method, etc.
* SPOPS/DBI/Pg.pm: Added first draft of PostgreSQL DBI
adapter. It's been tested and seems to work pretty well with
OpenInteract, which itself is a decent SPOPS test. Uses the
'SPOPS::Key::DBI::Sequence' class to implement auto-incrementing
fields.
* SPOPS/DBI/Sybase.pm: Use the now-standard 'increment_field'
boolean to see whether we're using an IDENTITY field for key
generation or not. (Thanks to Harry Danilevsky
<hdanilevsky@DeerfieldCapital.com> for pointing it out.)
* SPOPS/Impl/SecurityObj.pm: Lots of refactoring.
* SPOPS/Key/DBI/Sequence.pm: Added first draft of a sequence key
generator (found in many databases, including PostgreSQL and
Oracle).
* SPOPS/Secure.pm, SPOPS/Secure/Hierarchy.pm
- Renamed the field 'oid' to 'object_id' to get around databases
that use 'oid' as a reserved word (like PostgreSQL).
- Renamed the field 'level' to 'security_level' to get around
databases that have 'level' as a reserved word (like
Sybase). While this is a fairly fundamental change from the
database schema side of things, people simply using SPOPS as it
was intended shouldn't feel any changes except for altering the
table in which the security information is kept. (But that's an
implementation issue :-)
* SPOPS/Secure/Hierarchy.pm: Fixed the 'create_root_object' call to
SPOPS::Secure->set_security() to call the right method with the
right parameter types. Added some docs.
* SPOPS/SQLInterface.pm: Documented the value 'sth' for the
'return' parameter of db_select().
0.39 Sun Feb 25 13:50:56 EST 2001
* SPOPS.pm, SPOPS/Tie.pm, SPOPS/DBI.pm: Created a 'saved' flag
within the tied hash so that we always know whether a record has
been retrieved from a data store or not.
* SPOPS/DBI.pm: Prompted by Christian Lemburg
<clemburg@aixonix.de>: modify check_action_security() to see
whether the action is an add or not, and if so pass back
SEC_LEVEL_WRITE since SPOPS relies on your application to ensure
that a user can create an object. (Should SPOPS implement this?
Let us know on the openinteract-dev mailing list at
http://lists.sourceforge.net/lists/listinfo/openinteract-dev)
Also added documentation to the save() method.
* SPOPS/DBI.pm, SPOPS/Key/Random.pm, SPOPS/Key/DBI/*.pm: Modified
how the pre_fetch_id() and post_fetch_id() methods are called --
instead of passing a db handle/statement handle as the arguments,
instead put everything in a hashref and read from that. (Prompted
by the fact that SPOPS/Key/Random.pm was trying to use a DBI
database handle as the width for the random code to
generate. Ouch!)
* SPOPS/Configure.pm: Thanks to questions and notes from Leon
Brocard <acme@astray.com>, we added documentation for the 'has_a'
configuration item for an SPOPS object as well as gave developers
the ability to define a custom alias for the returned object
alias. For instance, you can do something like:
user => {
field => [ 'user_id', 'group_id', 'email' ],
id_field => 'user_id',
class => 'My::User',
has_a => { 'My::Group' => [ { group_id => 'zanybrainy' } ]
...,
},
and call the 'zanybrainy' method of a C<My::User> object to get
back the associated C<My::Group> object. (This is a silly example
but it means we don't have to rely solely on the clunky
automatic naming anymore, although it's still there if you want.)
* SPOPS/DBI/MySQL.pm: post_fetch_id() now first checks to see if
the 'increment_field' of the class configuration is set to a true
value and if so retrieves the value from the handle.
**THIS REQUIRES A CHANGE!!**
From now on to use AUTO_INCREMENT fields in MySQL and SPOPS, you
must set:
'increment_field' => 1
in the SPOPS configuration file for that class.
* SPOPS/DBI/Sybase.pm: post_fetch_id() now first checks to see if
the 'syb_identity' field of the class configuration is set to a
true value and if so retrieves the value from the handle. This is
no retrofit issue at all since we already required this.
* SPOPS/DBI.pm: _save_insert() now retrieves a db handle and
passes it to both the pre_fetch_id() and post_fetch_id()
methods as well as the appropriate (insert/update) SQLInterface
method.
* SPOPS.pm: added 'Storable' to the ISA list of SPOPS, which means
that every SPOPS object can now call store(), retrieve() and
nstore() get serialized in a network-transportable
format. Cool. Implications of this have yet to shake out,
however.
**POSSIBLE INTERFACE CONFLICT!!**
If you are using a routine with any of the following names:
store()
retrieve()
nstore()
then you will not be able to use the methods from Storable. If
you don't plan to use Storable then this is no big deal, but if
you do then you'll have to rename your class methods.
* SPOPS/Tie.pm: Functionality for doing field consistency checking
is now in the new SPOPS/Tie/StrictField.pm, which allows you to
create an SPOPS object without field restrictions and serialize
it to a datasource. This might not seem useful at first -- why
not just serialize a hashref? -- but we can treat all these
objects the same which will (trust me) be important to you at
some time or other. You also get builtin serialization and other
fun stuff for free.
* SPOPS/Key/Random.pm, SPOPS/Key/DBI/Pool.pm,
SPOPS/Key/DBI/Identity.pm, SPOPS/Key/DBI/HandleField.pm: Moved
all key-generation functions to the SPOPS::Key hierarchy. The
Identity.pm and HandleField.pm came out of SPOPS/DBI/Sybase.pm
and SPOPS/DBI/MySQL.pm, respectively, and Pool.pm is a rewritten
SPOPS/DBI/Keypool.pm. SPOPS/DBI/RandomCode.pm was just renamed to
Random.pm. (Hope that makes sense!) This might cause a little
pain but we'll see if we can keep backward compatibility.
* SPOPS.pm, SPOPS/DBI.pm, t/dbi.t: Applied patches from Rick Myers
<rik@sumthin.nu> to eliminate warnings regarding undefined items
under -w.
* SPOPS/GDBM.pm, t/gdbm.t: Applied patches from Rick Myers
<rik@sumthin.nu> to properly deal with GDBM_WRCREAT permissions
on new GDBM databases.
0.38 Thu Feb 1 00:54:03 EST 2001
Modified format of all files in the module to be more
readable. Also added to SPOPS.pm exportable methods _w (warn) and
_wm (warn multiple) which provides centralized debugging -- just
change the DEBUG value in SPOPS.pm to a number (higher for more
warnings) and all the packages in the modules will spill the
beans.
Applied patch from Christian Lemburg (lemburg@aixonix.de) to make
'parse_into_hash' manipulations work as intended in
SPOPS/Configure.pm.
0.37 Wed Jan 3 22:53:46 EST 2001
One small (but important) bugfix: an error in SPOPS::Secure was
using an object as a classname and instead of putting
'NS::MyObject' into the 'class' column of the 'sys_security'
table, it put something like 'NS::MyObject=HASH(0x8b7bb12)'.
0.36 Dec 20, 2000
SMALL API CHANGE: the return value from the 'fetch_by_object'
method in the security object class is now undef if there are no
security objects found and a hashref of security information if
there are security objects found. Unless you've been subclassing
SPOPS::Secure (or doing other funky stuff with it) you won't have
to worry about this.
Add the option to pass in a 'security_object_class' to any method
that neeeds it.
SPOPS/Secure/Hierarchy.pm has now been tested and works with some
simple cases. More work is needed to make the whole parsing thing
more flexible (e.g., if you pass a URL you want to only parse
everything after the hostname:port). Added lots of documentation
to this as well.
Updated SPOPS/Impl/SecurityObj.pm sample security object with
updates from OpenInteract usage. We don't require the thing being
protected to be an SPOPS object -- you can protect (and check
on) anything now, as long as you have a consistent categorical
identifier and a unique identifier for each thing.
0.35 Nov 18, 2000
Added SPOPS/Secure/Hierarchy.pm for finding security in a
parent-child relationship, like filesystems. This involved
exploding out some functionality from the 'get_security()' method
in SPOPS/Secure.pm so we could create an effective subclass. This
new security checking style (with the hierarchy) hasn't been
tested yet, but it's pretty straightforward and should work ok.
Moved certain functions out of SPOPS.pm and into new file
'SPOPS/Utility.pm' -- these are ones not really related to data
persistence but just helpful utilities.
NOTE: If you're updating to this version you need to put
'SPOPS::Utility' in your class 'isa' so that you can use the
methods now(), now_between_dates(), crypt_it(),
generate_random_code() and list_process().
COMPATIBILITY WARNING: Renamed the 'fetch_by_obj' method in the
security object (used in SPOPS/Secure.pm) to
'fetch_by_object'. This WILL NOT affect you if you're
just using the interface for SPOPS::Secure. (If you don't know
what that means, then you don't need to worry about it :)
(minor note) Changed all instances of RCS keyword '$Header' to
'$Id' and modified my email from cwinters@intes.net to
chris@cwinters.com.
0.34 Nov 10, 2000
Switched some silly documentation stuff in all the files. Changed
SPOPS/DBI.pm to pass $p to the pre/post action rulesets so that
the caller can affect the ruleset (e.g., 'full_text_skip =>
1'). Modified SPOPS::Configure::_read_code_class to be able to
read in multiple code classes when creating an SPOPS object and
added some documentation to that file for the 'fetch_by'
relationship. Modify SPOPS.pm to only respond to AUTOLOAD
requests when the thing doing the requesting is an object.
0.33 Oct 16, 2000
Added lots of documentation to individual files; transition to
'use constant' for debugging values (much gets optimized away at
compiletime). Ensure we're passing the database handle around in
all necessary operations using SPOPS/DBI.pm,
SPOPS/Configure/DBI.pm and SPOPS/SQLInterface.pm; added some
abstract methods in SPOPS.pm (such as CONFIG) so if subclasses do
not define them we don't get an error (duh). Add a new feature to
SPOPS/Configure.pm: define a list of fieldnames in your config
under the 'fetch_by' key and the system will create a subroutine
for you to retrieve objects by data contained in that
field. Updated the README file with some helpful installation
tips, including a pointer to the bundle file. Also modified most
(all?) places where we 'eval' code into existence to use a
localized $SIG{__WARN__} handler to throw out any messages passed
to STDERR so we can rely on our own error reporting
0.32 Sep 27, 2000
Added lots of documentation (including a 'README' file) and made
lots of changes to the configuration classes (SPOPS/Configure.pm
and SPOPS/Configure/DBI.pm; SPOPS/Configure/Ruleset.pm was
changed little) -- goal was to make it easier for some (but not
all) of the methods to be run, if necessary.and modified the
'create_initial_security' method of SPOPS/Secure.pm to have the
'code' method of generating initial security be responsible for
*all* the scopes. Also documented that method.
Added the 'no_security' configuration setting so that a class can
decide not to use security even if 'SPOPS::Secure' is in its
'isa'.
Added a couple of examples into the 'eg/' directory.
0.31 Sep 26, 2000
Removed the html encoding/decoding stuff from SPOPS.pm. Cleaned
up the initial_security stuff in SPOPS/Secure.pm -- better
comments with examples, better flow, etc. -- cleaned up the
documentation (formatting), and added a couple of TO DO items.
Added the necessary information to license SPOPS under the
Artistic/GPL licenses. Woo-hoo!
0.30 Sep 15, 2000
More tests, now for DBI. These are pretty simple and don't go
into security, but they should actually be quite useful to see if
the database (or rather, the DBD driver) supports the DBI
name/type inspection. I suspect that most 'modern' databases do
(or maybe that's just a hope). Also modified Makefile.PL to
collect information for testing.
The hope, was of course, immediately dashed. Current versions of
DBD::ASAny (Sybase Adaptive Server Anywhere) do not support
$sth->{TYPE}. So the planned modifications happened sooner than
expected. If your database does not support dynamic type
inspection, you can give simple type names in your configuration
('int', 'num', 'char', etc.) and SPOPS will translate them to the
DBI types for you.
0.29 Sep 14, 2000
Added the second implementation for storing SPOPS objects:
SPOPS/GDBM.pm. Tested with Interact::Package objects and so far
it's working well. Note that we don't deal with security or cache
checking yet -- I don't think those will be implemented until we
actually need them. (XP rubs off...). Also added the third
implementation for storing SPOPS objects: SPOPS/HashFile.pm. Use
it for reading in a perl hashref dumped (via Data::Dumper) to a
file. This simply reads in the file and provides key/value access
for any values you want to access and store. This implementation
actually uses its own tiehash implementation (packaged in the
same file) rather than SPOPS/Tie.pm. The XP stuff is definitely
rubbing off, because I'm starting to test everything. (Gasp!)
Added the t/ directory for testing and a testing suite for both
SPOPS/GDBM.pm and for SPOPS/HashFile.pm. DBI tests are going to
be a little more difficult...
0.28 Sep 11, 2000
Added the 'object_description' method to SPOPS.pm, since we
really do not have a generic way to query an object for a general
description, including a URL where we can get more information, a
name (general descriptive class of object) and a title
(information about a specific object). Modify SPOPS/Configure.pm
so we do not have to specify every single configuration key
needed for the class -- just those that we have to massage in
some way (e.g., an arrayref into a hashref) and let the rest just
get assigned. Let calls to $dbi_obj->save skip the logging and
security phases if they want. Also modify SPOPS/Configure.pm so
that we now create every class on the fly and read in the
subroutines if the configuration states that there is code behind
the class.
0.27 Aug 28, 2000
Moved all the ruleset initialization configuration actions to
SPOPS/Configure/Ruleset.pm. This involved moving the package
variable and accessor from SPOPS/Configure.pm and moving the
ruleset constructors from SPOPS/Configure/DBI.pm. Added
documentation about rulesets into SPOPS.pm; we should probably
take a lot of that general documentation and put it in its own
pod. Modified the '$item_remove' eval'd subroutine in
SPOPS/Configure/DBI.pm to use linking tables by default; it
should also work with one-to-many relationships that do not use
linking tables, but we might need to create a different set of
eval'd subroutines for such relationships. This probably doesn't
deserve its own 'version', but we need to get this out for
something :)
0.26 Aug 16, 2000
Minor changes to reflect that we never need to call a
global_error_class, and that the cache return has been named to
'global_cache' from 'global_cache_class'; also ducmented what
exactly the cache needs to do. Minor shifting around in
SPOPS/Configure.pm and SPOPS/Configure/DBI.pm as we modified
configuration stuff in OpenInteract. Modified SPOPS/Tie.pm to
track 'internal' variables -- first use will be for SPOPS/DBI.pm
methods to be able to validate data, particularly for 'Grouped'
objects. Split up 'save()' in SPOPS/DBI.pm into internal methods
'_save_insert()' and '_save_update()' to make it more
understandable.
Moved the 'check_action_security()' method from SPOPS/Secure.pm
to SPOPS/DBI.pm and put additional functionality in. Now we
simply pass an object and a required level -- do not wrap it in
an eval{} and it will throw any necessary errors for you. Added
the default 'check_security' method to SPOPS.pm so
non-SPOPS::Secure classes will inherit the 'everything goes'
security.
Finally, added some cool functionality to ensure that pre/post
actions actually mean something. At initialization time, we
scour the parents of a SPOPS object class for rules to execute
during these phases. These rules can be simple or complex, and
they must be exclusive of one another and execute in any order.
This will allow us to do interesting things with layers of
meta-information on top of objects. For example, any class
inheriting from 'SPOPS::Searchable' will submit its information
to a separate class, which processes it and adds/modifies a
full-text search engine. Cool stuff :)
0.25 Aug 2, 2000
Fixed a long-dormant buglet: this entire time, the
SPOPS/DBI/save method was trying to retrieve the just-set values
of fields with DEFAULT settings from the database so the object
could be in sync at all times; turns out that we didn't tell
SQLInterface to return a single row so we were assigning a
stringified arrayref (eg: ARRAY(0xADFE21)) to the first value
and nothing to the others... Good thing caching isn't activated
yet :)
0.24 Jul 26, 2000
Fixed some typos in SPOPS/Configure/DBI.pm in the eval'd
routines (including one where we were fetching the linked
objects but not returning them -- duh!); changed how
SPOPS/Error.pm gets the caller information. Added the ability of
SPOPS/Configure.pm to put any coderefs where they
belong. Modified SPOPS/DBI.pm to take the old ID to update by in
the save() method, so you can update the primary key field and
still refer to the old ID.
0.23 Jul 15, 2000
Radically changed how errors are thrown. Every error generated
by SPOPS now sets the package variables user_msg, system_msg,
type and (optionally) extra in the brand new module,
SPOPS::Error. It then dies with the simple scalar user_msg,
which is what the caller catches. The caller is then responsible
for retrieving as much information as it needs from the
SPOPS::Error package. The main reason we're doing this is to
eliminate problems arising from overriding the CORE::die
function in perl. Also added some documentation to lots of
modules. Added the module SPOPS/Impl/User.pm as an example of
what a SPOPS user object looks like and should do.
0.22 Jul 14, 2000
Modified a couple items in SPOPS/DBI.pm: the fetch_group()
method can now take multiple tables and do a join but still
return a group of objects that match the criteria. To do this we
also changed the id_clause() method to fully-qualify ID fields
unless otherwise told. Also changed the alias subroutine in
SPOPS/Configure/DBI.pm created for links_to relationships to
*not* qualify the ID field, since we frequently use link tables
to accomplish this. Also flipped some items around in the has_a
alias created in SPOPS/Configure.pm. And in a minor item, change
the die {} idiom to use a preceding "if ( $@ )" clause rather
than a trailing one, since the latter is too difficult to pick
out easily. Modify the SPOPS/Configure/DBI.pm _add and _remove
aliases to take either a single ID or an arrayref of IDs.
0.21 Jun 27, 2000
Updated to put all internal dependencies (like @ISA) into
explicit 'use' statements rather than depend on mod_perl or
something else loading everything else up for us. Modified
SPOPS/Configure/DBI.pm to wrap the fetch() call for a links_to
relationship in an eval {} so we don't catch any errors thrown
with die up top. SPOPS/generate_random_code can now return
mixed-case codes. Also changed SPOPS/DBI.pm to return undef if a
particular record simply isn't found -- for some reason, I
thought this had been in there since the beginning.
0.20 Jun 18, 2000
Paper bag fix in SPOPS/Secure.pm made by renaming a hash. <blush>
0.19 Jun 18, 2000
Added and reworked a ton of documentation to SPOPS/DBI.pm; also
added a super-easy and super-cool feature to its fetch()
method: every time you ask for an object, the system will also
give its security level to you, free of charge. Just look in
the {tmp_security_level} property. Made a tiny change to
SPOPS/Impl/SecurityObj.pm to ensure that users not logged in do
not automatically belong to all existing groups :) Put
modification into SPOPS/DBI/fetch and fetch_group to allow
method calls to skip security checks to get around a
chicken-and-egg problem with logging in as the superuser. (Only
the superuser object can see the superuser object, so how are
we going to fetch the object to login before we've logged in
yet? :)
0.18 Jun 14, 2000
Modified how errors are handled within all SPOPS modules. This
forced changes throughout a number of modules, but particularly
SPOPS/DBI.pm, SPOPS/Secure.pm and SPOPS/SQLInterface.pm. Added
documentation to SPOPS/Tie.pm and modified it to use Carp::carp
when something tries to access/modify a property that is not in
the field list. Modified other modules to use Carp::carp when
reporting nonfatal errors that require some modification from
the method call. Also modified SPOPS/Tie.pm to take an arrayref
(versus a hashref) argument for the list of fields it will
accept.
0.17 Jun 13, 2000
Modified all parameters to be passed as hashrefs rather than
hashes. Passing-by-hashref was implemented in more recent
modules (such as SPOPS/Secure.pm), but was not retrofitted to
older modules. Finally bit the bullet and did this. More
testing for object security stuff -- nothing amiss so
far. Added some configuration options for as_string method to
make the output look a little nicer and modified SPOPS/AUTOLOAD
(again) to not warn with an error if it cannot find a value for
a 'tmp_' variable. Modified the SPOPS/clone() method to force
the ID field of the new object to be blank.
0.16 Jun 9, 2000
Fixed SPOPS/Configure.pm to have the alias of the 'has_a'
subroutine created to match the type of object it 'has' only if
the ID field is exactly the same; fixed dumb mistake in
SPOPS/Secure.pm that wasn't differentiating properly between a
class and an object in ->create_initial_security; made a fairly
simple patch to SPOPS/Tie.pm which allows you to store
temporary attributes for the life of the object -- when the
object is DESTROYed, there go the attribs, and they're not
saved when the object is cached. Very useful for tracking
access rights on a per-object basis. We also had to make a
change to SPOPS/AUTOLOAD so we could call the temporary key as
a subroutine reference (Template Toolkit does this, somewhat
annoyingly...). Also tested out object security stuff -- works
pretty well.
0.15 Jun 8, 2000
Modified the SQL-generation in SPOPS/Impl/SecurityObj.pm to
properly fetch WORLD-only requests; if there is no user object
existing and $class->check_security; is called, we want to
return only the WORLD permissions (SPOPS/Secure.pm); modified
the API for object security checking so that only one method
is called (SPOPS/Secure/check_action_security) and the action
is passed as the first argument; created the
SPOPS/Secure/create_initial_security method that allows us to
initialize an object with security information. Tested
check_action_security and fixed related API boo-boos in
SPOPS/DBI.pm. Individual object as well as class-based
security now seems to be working well, although more testing
is certainly required :)
0.14 Jun 7, 2000
Allow user to pass information to fetch() method (via both fetch()
and fetch_group() that overrides configuration for the field_alter
key (refresher: allows you to set a format on a returned value
from the database, such as DATE_FORMAT( mydate, '%b %e' )); massive
changes to SPOPS/Secure.pm to allow it to work with class-only and data
object security transparently (not a lot of testing yet,
however); SPOPS/Impl/SecurityObj.pm also had some changes to
enable it to work with classes
0.13 Jun 5, 2000
Added SPOPS/Impl/SecurityObj.pm to the distribution as a DBI-based
SPOPS object representing security; added methods and documentation
to SPOPS/Secure.pm; modified SPOPS/Configure.pm to allow multiple
aliases per type in the 'has_a' descriptor.
0.12 May 29, 2000
Added list_process() method to SPOPS.pm and added the related
methods to SPOPS::Configure::DBI %alias_to%_remove and
%alias_to%_add, which allow us to link together objects without
actually modifying the objects. For instance, if the alias
'group' has defined: links_to => [ 'user' ] the methods would
be named 'user_add' and 'user_remove' and would add or remove
one or more users to the group. Also modified all the errors
thrown in SQLInterface to have the proper type and at least
some semblance of informative messages.
0.11 May 24, 2000
Paper bag fix in SPOPS::Tie (changes weren't being tracked and
therefore updates not saved); paper bag fix in SPOPS::Configure::DBI
(bad definition for eval'd subroutine created *evil* bug of
super-rapid memory consumption); added data() method to SPOPS.pm;
routine created in SPOPS::Configure and eval'd for has_a now
only tries a fetch if the ID value exists
0.10 May 17, 2000
Polishing; things basically work
0.01 May 12, 2000
Original version - much of the packages were brought in from elsewhere,
but a lot of work is needed to extricate them from their
previous application and to make everything work together and
feel cohesive.