persistence.rb

Path: highcrest/persistence.rb
Last Update: Wed May 26 14:21:41 GMT Daylight Time 2004

This library can be included to add database persistence to a domain class. The module:

  • uses database meta-data to derive as much information as possible, so not requiring a separate config file.
  • supports whatever types your DBD driver will support, including LOBs and LONGs.
  • supports automatic persistence of child objects along with the parent object.
  • supports optimistic locking.
  • can be extensively and easily customised at a per-application, per-class, or per-field level.

Usage

Assuming the table: CREATE TABLE FOO (ID VARCHAR(32) PRIMARY KEY, DESCRIPTION VARCHAR(1000))

Require the library for your DBD driver

  require 'highcrest/persistence/oci8'
    ==>true

Declare a persistent class

  class Foo
    include Highcrest::Persistence
    attr_accessor :id, :description
  end
    ==>nil

Create and insert a new object with generated key

  begin
    foo = Foo.new
    foo.description = 'I am a Foo'
    foo.replace!
    Foo.dbh.commit
    foo
  rescue Exception => ex
    Foo.dbh.rollback
    raise ex
  end
    ==>#<Foo:0x31ad5a8 @description="I am a Foo", @id=105681042.0, @optimistic_values={"ID"=>105681042.0, "DESCRIPTION"=>"I am a Foo"}>

Create and insert a new object with assigned key

  begin
    foo2 = Foo.new
    foo2.id = 'key'
    foo2.description = 'I am a Foo'
    foo2.insert!
    Foo.dbh.commit
    foo2
  rescue Exception => ex
    Foo.dbh.rollback
    raise ex
  end
    ==>#<Foo:0x31a1d40 @description="I am a Foo", @id="key", @optimistic_values={"ID"=>"key", "DESCRIPTION"=>"I am a Foo"}>

Retrieve and update existing object

  begin
    foo2 = Foo.read 'key'
    foo2.description = 'I am a Foo too'
    foo2.replace!
    Foo.dbh.commit
    foo
  rescue Exception => ex
    Foo.dbh.rollback
    raise ex
  end
    ==>#<Foo:0x31ad5a8 @description="I am a Foo", @id=105681042.0, @optimistic_values={"ID"=>105681042.0, "DESCRIPTION"=>"I am a Foo"}>

Delete an object

  begin
    foo.delete!
    Foo.dbh.commit
    foo
  rescue Exception => ex
    Foo.dbh.rollback
    raise ex
  end
    ==>#<Foo:0x31ad5a8 @description="I am a Foo", @id=105681042.0, @optimistic_values=nil>

Search for lightweight DBI rows with partial match

  begin
    rows = Foo.search 'description'=>'I am a Foo'
  rescue Exception => ex
    Foo.dbh.rollback
    raise ex
  end
    ==>[["key", "I am a Foo too"]]

Essential Customisations

  1. Database Handle method
  2. Id generation method

I recommend that you create a patch file to redefine these methods, and also define any type-conversions required by your DBD driver. See highcrest/persistence for some oracle examples.

Commits, Rollbacks and Transactions

I assume that transactions are controlled from a higher-level tier in your application. You are responsible for setting the commit policy on your database handle, and calling commit or rollback.

(Of course you can easily redefine the main CRUD methods to be transactional if that fits your requirements.)

Other Customisations

FIXME Column- and type conversions, search-conversions, sql generators.

  1. Replace the default mapping for a particular database-column-type by defining to_type_TYPE_NAME and from_type_TYPE_NAME methods.
  2. Replace the default mapping for a particular database-column by defining to_column_COLUMN_NAME and from_column_COLUMN_NAME methods.
  3. Replace the default SQL generated for a particular search FIXME

Optimistic locking

Updates and deletes are automatically subject to optimistic locking. Whenever an object is loaded from the database, or successfully saved, a hash of the database’s current values for this record is created. Subsequent attempts to update the database are subject to a where clause based on those values. If another user has updated the record in the meantime, then no rows will be updated and a RecordNotFoundError will be thrown.

LOBs and LONGs are not included in optimistic locking tests.

LOB Support

LONGs are supported if the underlying DBD driver supports them.

LOBs can also be supported, but write_lobs, from_type_LOB and to_type_LOB methods must all be defined. See highcrest/persistence/oci8.rb for an oracle solution.

Note however that this module is not an appropriate solution for very large objects, because the entire object is read into memory. In scenarios where this is impractical, I suggest that you stream data from the persistent store to the presentation layer without going through an object model.

Examples

See highcrest/persistence for a set of patches for use with Oracle DBD drivers. See highcrest/persistence/example for some sample class files. See highcrest/persistence/test for a fairly comprehensive set of unit tests. And see recipes.txt for a set of code-fragments for common scenarios.


Copyright 2004 Graham Robert Jenkins

I don’t mind what you do with this code as long as you acknowledge my contribution.

Required files

dbi  

[Validate]