NAME

Gantry::Utils::Model - a general purpose Object Relational Model base class

SYNOPSIS

use base 'Gantry::Utils::Model';

sub get_table_name     { return 'your_table';                           }
sub get_sequence_name  { return 'your_table_seq';                       }
sub get_primary_col    { return 'id';                                   }
sub get_quoted_cols    { return { text_col => 1, other_text_col => 1 }; }
sub get_essential_cols { return 'id, text_col';                         }

sub get_primary_key    { goto &get_id;                                  }

sub set_id             { croak "Can't change primary key";              }
sub get_id             { return $_->[0]{id};                            }

sub set_text_col       {
    my $self  = shift;
    my $value = shift;

    $self->{text_col} = $value;
    $self->{__DIRTY__}{text_col}++;
    return $value;
}
sub get_text_col       { return $_->[0]{text_col};                      }

sub set_other_text_col {
    my $self  = shift;
    my $value = shift;

    $self->{other_text_col} = $value;
    $self->{__DIRTY__}{other_text_col}++;

    return $value;
}
sub get_other_text_col {
    my $self = shift;
    unless ( defined $self->{other_text_col} ) {
        $self->lazy_fetch( 'other_text_col' );
    }
    return $self->{other_text_col};
}

DESCRIPTION

This module is a Class::DBI replacement. Its goal is to reduce the mystery in in the internals of that module, while still providing most of its functionality. You'll notice that the inheriting class has a lot more code than a Class::DBI subclass would. This is because we use Bigtop to generate the subclasses. Thus, we don't care so much about the volume of code. The result is code which is easy to read, understand, and modify.

RATIONALE

Class::DBI and its cousins provide a beautiful API for client code. By implementing straightforward database row to Perl object correspondence, they save a lot of mental effort when writing most applications.

They do have drawbacks. My premise is that most of these drawbacks stem from a single fundamental design descision. Perl's traditional Object Relation Mappers (ORMs) do a lot of work at run time. Namely, they build accessors at run time. When I first started using them, I thought this was gorgeous. Class::DBI::mysql was one of my favorite modules. I bought the promise of a future where all you had to say was something like

package MyModel;

use Class::DBI::SuperClever
    'dbi:Pg:dbname=somedb', 'user', 'passwd', 'MyModel';

and the whole somedb database would be mapped without another word. Each table would become a class under MyModel with an accessor for each column. Then I could create, retrieve, update, and delete to my hearts content while beholding the power of Perl.

The problem is that use statements like the above example require extreme magic (and not a small amount of time). This leads to a lack of transperency which leaves me with three problems: (1) I worry, in the back of my mind I always have the doubt of not knowing what is going on in these complex beasts (2) I get hit by subtle bugs, like name collisions from inheritence and inadvertant overriding (3) worst, I am left with a system that works really well to do the things the author thought of, but not the thing I really need to do in a particular instance (either because the system is inherently limiting or more likely because it is so complex I can't wrap my small mind around it well enough to carry out my task).

This leads to the fundamental principle of this module: simplicity. Any programmer with intermediate Perl skills and a passing familiarity with SQL databases should be able to digest this in a morning. There are other goals, but simplicity is at the core.

In order to achieve transperency, it is necessary to have more code in the subclasses. This is really why the magical schemes sprang up. But, recently I have been working on generation of code. This amounts to the same thing, but it happens ahead of time. So, instead of code being generated by magic during run time, my code is generated by simple grammar based parsing before compile time. The generator in question is bigtop which can build a completely funcational web app from a description of its data models and controllers. Then, when a programmer wonders what the model is up to, she has a set of simple modules which explicitly show what is going on.

METHODS PROVIDED BY THIS MODULE

disconnect

Class or instance method. You can pass in a handle or this will call db_Main to get the standard one. In either case, it will rollback any current transaction (if you aren't auto-committing) and disconnect the handle.

dbi_commit

Class or instance method. By default the dbh managed by this module has AutoCommit off. Call this to commit your transactions.

construct

Class method. Mainly for internal use. This method takes a hash (usually one bound to a statement handle) and turns it into an object of the subclass through which it was called.

special_sql

Class method. Accepts sql SELECT statements returns a list (not a reference or iterator) of objects in the class through which it was called. Be careful with column names, they need to be the genuine names of columns in the underlying table.

retrieve_all

Class method. Pass a list of key/value pairs or a single hash ref. The only legal key is order_by, its value will be used literally directly after 'ORDER BY' (that means, don't include the ORDER BY keywords in your value). Returns a list of objects.

retrieve_by_pk

Class method. Pass a single primary key value. Returns the row with that primary key value as an object or undef if no such row is found.

retrieve

Class method. Similar to retrieve in Class::DBI. If called with one argument, that argument is taken as a primary key and the request is forwarded to retrieve_by_pk. If called with multiple arguments (or no arguments), those arguments are forwarded to search.

Class method. Similar to search in Class::DBI. Call with the key/value pairs you want to match in a simple list. Add a single hash reference as the last parameter if you like. Currently that hash reference may only contain an order_by key. Its value is used to fill in the blank 'ORDER BY ___'.

Returns a list of objects one each for every row that matched the search criterion.

lazy_fetch

Instance method. Call with the column name you want to fetch. Returns nothing useful, but sets the column with the value from the corresponding row in the underlying table.

create

Class method. Call with a hash reference whose keys are the column names you want to populate. Any value will be quoted if its value in the get_quoted_cols hash is defined.

_next_primary_key

Class method. Returns the next value of the sequence associated with the underlying table.

find_or_create

Class method. Call with a hash reference of search criteria (think of a WHERE clause). First, it calls search, taking a single resulting object. If that works, you get the object. Otherwise, it calls create with your hash reference and returns the new object.

update

Instance method. Issues an UPDATE to SET the dirty values from the invocant. Returns nothing useful, although it could die if the dbh has problems.

delete

Instance method. Deletes the underlying row from its table and renders the invocant reference unusable.

get

Instance method. Call with a list of columns whose values you want. Returns the values in the invocant for the columns you requested. If you requested only one column a scalar is returned. Otherwise, you get a list.

set

Instance method. Call with a list of key/value pairs for columns that you want to change. Returns nothing useful.

quote_attribute

Instance method. Primarily for internal use. Call with a column name. Returns the value in the column quoted so SQL will take it.

quote_scalar

Class or instance method. Call with a column name and a value. Returns the value quoted for SQL as if it were stored in the column of an object. Even if you call this as an instance method, the instance values are not used.

METHODS SUBCLASSES MUST PROVIDE

You can include any useful method you like in your subclass, but these are the ones this module needs.

get_table_name

Return the name of the table in the database that your class models.

get_sequence_name

Return the name of the sequence associated with your table. This is needed for the create method.

get_essential_cols

Return an array reference containing the columns you want to fetch automatically during retrieve, search, etc.

get_primary_col

We assume that each table has a unique primary key (though we assume nothing about its name). Return the name of that column.

get_quoted_cols

Return a hash reference whose keys are the names of columns whose values should be single quoted in SQL statements. The values are unimportant, so long as they are defined.

get_primary_key

An instance method. Return the value of the primary key for the invocant.

set_COL_NAME

Provide one of these for each column. Called on an existing object with a new value. It must store the value in the object's hash (whose keys are the column labels) AND set the dirty flag for the column so that eventual updates will be effective. Some callers may expect to receive the new value in return, document whether it returns that value or not. Example:

sub set_amount {
    my $self  = shift;
    my $value = shift;

    $self->{amount} = $value;
    $self->{__DIRTY__}{amount}++;

    return $self->{amount};
}
get_COL_NAME

Provide one of these for each column. Return the unquoted value in the column. Example:

sub get_amount {
    my $self = shift;

    return $self->{amount};
}
COL_NAME (completely optional)

Provide one of these for each column only if you like. Dispatch to get_ and set_ methods based on the arguments you receive. These methods are NEVER called internally, but your callers might like them. Example (with apologies to Dr. Conway):

sub amount {
    my $self  = shift;
    my $value = shift;

    if ( defined $value ) { return $self->set_amount( $value ); }
    else                  { return $self->get_amount();         }
}

OMISSIONS

There is no caching. This means two things: (1) no sql statement is prepared with bind parameter place holders and stored for possible reuse (2) objects are always built for each row retrieved, even if there is a live object for that row elsewhere in memory.

There are no triggers. If you need these, put them in the accessors as needed. Feel free to override construct.

There are no iterators. Class::DBI makes iterators, but they only delay object instantiation, the full query results are pulled from the beginning. Replicating that behavior seems like the pursuit of diminishing returns.

AUTHOR

Phil Crow <philcrow2000@yahoo.com>

COPYRIGHT and LICENSE

Copyright (c) 2006, Phil Crow.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.