NAME

DBIx::NinjaORM - Flexible Perl ORM for easy transitions from inline SQL to objects.

VERSION

Version 2.0.0

DESCRIPTION

Caveat: if you're starting a new project, DBIx::NinjaORM is probably not the right ORM to use. Look instead at DBIx::Class for example, which has a more abstract model and can leverage a nicely normalized database schema.

DBIx::NinjaORM was designed with a few goals in mind:

  • Allow a progressive introduction of a separate Model layer in a legacy codebase.

  • Expand objects with data joined from other tables, to do less queries and prevent lazy-loading of ancillary information.

  • Have a short learning curve.

SYNOPSIS

Simple example

Let's take the example of a My::Model::Book class that represents a book. You would start My::Model::Book with the following code:

package My::Model::Book;

use strict;
use warnings;

use base 'DBIx::NinjaORM';

use DBI;

sub static_class_info
{
	my ( $class ) = @_;
	
	# Retrieve defaults from DBIx::Ninja->static_class_info().
	my $info = $class->SUPER::static_class_info();
	
	# Set mandatory defaults.
	$info->{'table_name'} = 'books';
	$info->{'primary_key_name'} = 'book_id';
	$info->{'default_dbh'} = DBI->connect(
		"dbi:mysql:[database_name]:localhost:3306",
		"[user]",
		"[password]",
	);
	
	# Add optional information.
	# Allow filtering SELECTs on books.name.
	$info->{'filtering_fields'} = [ 'name' ];
	
	return $info;
}

1;

Inheriting with use base 'DBIx::NinjaORM' and creating sub static_class_info (with a default database handle and a table name) are the only two requirements to have a working model.

A more complex model

If you have more than one Model class to create, for example My::Model::Book and My::Model::Library, you probably want to create a single class My::Model to hold the defaults and then inherits from that main class.

package My::Model;

use strict;
use warnings;

use base 'DBIx::NinjaORM';

use DBI;
use Cache::Memcached::Fast;

sub static_class_info
{
	my ( $class ) = @_;
	
	# Retrieve defaults from DBIx::Ninja->static_class_info().
	my $info = $class->SUPER::static_class_info();
	
	# Set defaults common to all your objects.
	$info->{'default_dbh'} = DBI->connect(
		"dbi:mysql:[database_name]:localhost:3306",
		"[user]",
		"[password]",
	);
	$info->{'memcache'} = Cache::Memcached::Fast->new(
		{
			servers =>
			[
				'localhost:11211',
			],
		}
	);
	
	return $info;
}

1;

The various classes will then inherit from My::Model, and the inherited defaults will make static_class_info() shorter in the other classes:

package My::Model::Book;

use strict;
use warnings;

# Inherit from your base model class, not from DBIx::NinjaORM.
use base 'My::Model';

sub static_class_info
{
	my ( $class ) = @_;
	
	# Retrieve defaults from My::Model.
	my $info = $class->SUPER::static_class_info();
	
	# Set mandatory defaults for this class.
	$info->{'table_name'} = 'books';
	$info->{'primary_key_name'} = 'book_id';
	
	# Add optional information.
	# Allow filtering SELECTs on books.name.
	$info->{'filtering_fields'} = [ 'name' ];
	
	return $info;
}

1;

SUPPORTED DATABASES

This distribution currently supports:

  • SQLite

  • MySQL

  • PostgreSQL

Please contact me if you need support for another database type, I'm always glad to add extensions if you can help me with testing.

SUBCLASSABLE METHODS

DBIx::NinjaORM is designed with inheritance in mind, and you can subclass most of its public methods to extend or alter its behavior.

This group of method covers the most commonly subclassed methods, with examples and use cases.

static_class_info()

This methods sets defaults as well as general information for a specific class. It allows for example indicating what table the objects will be related to, or what database handle to use.

Here's what a typical subclassed static_class_info() would look like:

sub static_class_info
{
	my ( $class ) = @_;
	
	# Retrieve defaults coming from higher in the inheritance chain, up
	# to DBIx::NinjaORM->static_class_info().
	my $info = $class->SUPER::static_class_info();
	
	# Set or override information.
	$info->{'table_name'} = 'books';
	$info->{'primary_key_name'} = 'book_id';
	$info->{'default_dbh'} = DBI->connect(
		"dbi:mysql:[database_name]:localhost:3306",
		"[user]",
		"[password]",
	);
	
	# Return the updated information hashref.
	return $info;
}

Here's the full list of the options that can be set or overridden:

  • default_dbh

    The database handle to use when performing queries. The methods that interact with the database always provide a dbh argument to allow using a specific database handle, but setting it here means you won't have to systematically pass that argument.

    $info->{'default_dbh'} = DBI->connect(
    	"dbi:mysql:[database_name]:localhost:3306",
    	"[user]",
    	"[password]",
    );
  • memcache

    Optionally, DBIx::NinjaORM uses memcache to cache objects and queries, in conjunction with the list_cache_time and object_cache_time arguments.

    If you want to enable the cache features, you can set this to a valid Cache::Memcached object (or a compatible module, such as Cache::Memcached::Fast).

    $info->{'memcache'} = Cache::Memcached::Fast->new(
    	{
    		servers =>
    		[
    			'localhost:11211',
    		],
    	}
    );
  • table_name

    Mandatory, the name of the table that this class will be the interface for.

    # Interface with a 'books' table.
    $info->{'table_name'} = 'books';
  • primary_key_name

    The name of the primary key on the table specified with table_name.

    $info->{'primary_key_name'} = 'book_id';
  • list_cache_time

    Control the list cache, which is an optional cache system in retrieve_list() to store how search criteria translate into object IDs.

    By default it is disabled (with undef), but it is activated by setting it to an integer that represents the cache time in seconds.

    # Cache for 10 seconds.
    $info->{'list_cache_time'} = 10;
    
    # Don't cache.
    $info->{'list_cache_time'} = undef;

    A good use case for this would be retrieving a list of books for a given author. We would pass the author ID as a search criteria, and the resulting list of book objects does not change often. Provided that you can tolerate a 1 hour delay for a new book to show up associated with a given author, then it makes sense to set the list_cache_time to 3600 and save most of the queries to find what book otherwise belongs to the author.

  • object_cache_time

    Control the object cache, which is an optional cache system in retrieve_list() to store the objects returned and be able to look them up by object ID.

    By default it is disabled (with undef), but it is activated by setting it to an integer that represents the cache time in seconds.

    # Cache for 10 seconds.
    $info->{'object_cache_time'} = 10;
    
    # Don't cache.
    $info->{'object_cache_time'} = undef;

    A good use case for this are objects that are expensive to build. You will see more in retrieve_list() on how to cache objects.

  • unique_fields

    The list of unique fields on the object.

    Note: DBIx::NinjaORM does not support unique indexes made of more than one field. If you add more than one field in this arrayref, the ORM will treat them as separate unique indexes.

    # Declare books.isbn as unique.
    $info->{'unique_fields'} = [ 'isbn' ];
    
    # Declare books.isbn and books.upc as unique.
    $info->{'unique_fields'} = [ 'isbn', 'upc' ];
  • filtering_fields

    The list of fields that can be used to filter on in retrieve_list().

    # Allow filtering based on the book name and author ID.
    $info->{'unique_fields'} = [ 'name', 'author_id' ];
  • readonly_fields

    The list of fields that cannot be set directly. They will be populated in retrieve_list, but you won't be able to insert / update / set them directly.

  • has_created_field

    Indicate whether the table has a field name created to store the UNIX time at which the row was created. Default: 1.

    # The table doesn't have a 'created' field.
    $info->{'has_created_field'} = 0;
  • has_modified_field

    Indicate whether the table has a field name modified to store the UNIX time at which the row was modified. Default: 1.

    # The table doesn't have a 'modified' field.
    $info->{'has_modified_field'} = 0;
  • cache_key_field

    By default, the object cache uses the primary key value to make cached objects available to look up, but this allows specifying a different field for that purpose.

    For example, you may want to use books.isbn instead of books.book_id to cache objects:

    $info->{'cache_key_field'} = 'isbn';
  • verbose

    Add debugging and tracing information, 0 by default.

    # Show debugging information for operations on this class.
    $info->{'verbose'} = 1;
  • verbose_cache_operations

    Add information in the logs regarding cache operations and uses.

new()

new() has two possible uses:

  • Creating a new empty object

    my $object = My::Model::Book->new();
  • Retrieving a single object from the database.

    # Retrieve by ID.
    my $object = My::Model::Book->new( id => 3 )
    	// die 'Book #3 does not exist';
    
    # Retrieve by unique field.
    my $object = My::Model::Book->new( isbn => '9781449303587' )
    	// die 'Book with ISBN 9781449303587 does not exist';

As a result, new() accepts the following arguments:

  • id

    The ID for the primary key on the underlying table. id is an alias for the primary key field name.

    my $object = My::Model::Book->new( id => 3 )
    	// die 'Book #3 does not exist';
  • A unique field

    Allows passing a unique field and its value, in order to load the corresponding object from the database.

    my $object = My::Model::Book->new( isbn => '9781449303587' )
    	// die 'Book with ISBN 9781449303587 does not exist';
  • skip_cache (default: 0)

    By default, if cache is enabled with object_cache_time() in static_class_info(), then new attempts to load the object from the cache first. Setting skip_cache to 1 forces the ORM to load the values from the database.

    my $object = My::Model::Book->new(
    	isbn       => '9781449303587',
    	skip_cache => 1,
    ) // die 'Book with ISBN 9781449303587 does not exist';
  • lock (default: 0)

    By default, the underlying row is not locked when retrieving an object via new(). Setting lock to 1 forces the ORM to bypass the cache if any, and to lock the rows in the database as it retrieves them.

    my $object = My::Model::Book->new(
    	isbn => '9781449303587',
    	lock => 1,
    ) // die 'Book with ISBN 9781449303587 does not exist';

commit()

Convenience function to insert or update the object.

If the object has a primary key set, update() is called, otherwise insert() is called. If there's an error, the method with croak with relevant error information.

$book->commit();

Arguments: (none).

remove()

Delete in the database the row corresponding to the current object.

$book->remove();

This method accepts the following arguments:

  • dbh

    A different database handle from the default specified in static_class_info(). This is particularly useful if you have separate reader/writer databases.

validate_data()

Validate the hashref of data passed as first argument. This is used both by insert() and update to check the data before performing databse operations.

my $validated_data = $object->validate_data(
	\%data,
);

If there is invalid data, the method will croak with a detail of the error.

get()

Get the value corresponding to an object's field.

my $book_name = $book->get('name');

This method will croak if you attempt to retrieve a private field. It also detects if the object was retrieved from the database, in which case it has an exhaustive list of the fields that actually exist in the database and it will croak if you attempt to retrieve a field that doesn't exist in the database.

set()

Set fields and values on an object.

$book->set(
	{
		name => 'Learning Perl',
		isbn => '9781449303587',
	},
);

This method supports the following arguments:

  • force

    Set the properties on the object without going through validate_data().

    $book->set(
    	{
    		name => 'Learning Perl',
    		isbn => '9781449303587',
    	},
    	force => 1,
    );

clone()

Clone the current object and return the clone.

my $cloned_book = $book->clone();

insert()

Insert a row corresponding to the data passed as first parameter, and fill the object accordingly upon success.

my $book = My::Model::Book->new();
$book->insert(
	{
		name => 'Learning Perl',
	}
);

If you don't need the object afterwards, you can simply do:

My::Model::Book->insert(
	{
		name => 'Learning Perl',
	}
);

This method supports the following optional arguments:

  • overwrite_created

    A UNIX timestamp to be used instead of the current time for the value of 'created'.

  • generated_primary_key_value

    A primary key value, in case the underlying table doesn't have an autoincremented primary key.

  • dbh

    A different database handle than the default one specified in static_class_info(), but it has to be writable.

  • ignore

    INSERT IGNORE instead of plain INSERT.

$book->insert(
	\%data,
	overwrite_created           => $unixtime,
	generated_primary_key_value => $value,
	dbh                         => $dbh,
	ignore                      => $boolean,
);

update()

Update the row in the database corresponding to the current object, using the primary key and its value on the object.

$book->update(
	{
		name => 'Learning Perl',
	}
);

This method supports the following optional arguments:

  • skip_modified_update (default 0)

    Do not update the 'modified' field. This is useful if you're using 'modified' to record when was the last time a human changed the row, but you want to exclude automated changes.

  • dbh

    A different database handle than the default one specified in static_class_info(), but it has to be writable.

  • restrictions

    The update statement is limited using the primary key. This parameter however allows adding extra restrictions on the update. Additional clauses passed here are joined with AND.

    $book->update(
    	{
    		author_id => 1234,
    	},
    	restrictions =>
    	{
    		where_clauses => [ 'status != ?' ],
    		where_values  => [ 'protected' ],
    	},
    );
  • set

    \%data contains the data to update the row with "SET field = value". It is however sometimes necessary to use more complex SETs, such as "SET field = field + value", which is what this parameter allows.

    Important: you will need to subclass update() in your model classes and update manually the values upon success (or reload the object), as DBIx::NinjaORM cannot determine the end result of those complex sets on the database side.

    $book->update(
    	{
    		name => 'Learning Perl',
    	},
    	set =>
    	{
    		placeholders => [ 'edits = edits + ?' ],
    		values       => [ 1 ],
    	}
    );

retrieve_list_nocache()

Dispatch of retrieve_list() when objects should not be retrieved from the cache.

See retrieve_list() for the parameters this method accepts.

UTILITY METHODS

retrieve_list()

Return an arrayref of objects matching all the criteria passed.

This method supports the following filtering criteria:

  • id

    An ID or an arrayref of IDs corresponding to the primary key.

    # Retrieve books with ID 1.
    my $books = My::Model::Book->retrieve_list(
    	id => 1,
    );
    
    # Retrieve books with IDs 1, 2 or 3.
    my $books = My::Model::Book->retrieve_list(
    	id => [ 1, 2, 3 ]
    );
  • Field names

    A scalar value or an arrayref of values corresponding to a field listed in static_class_info() under either filtering_fields or unique_fields.

    # Retrieve books for an author.
    my $books = My::Model::Book->retrieve_list(
    	author_id => 12,
    );
    
    # Retrieve books by ISBN.
    my $books = My::Model::Book->retrieve_list(
    	isbn =>
    	[
    		'9781449313142',
    		'9781449393090',
    	]
    );

This method also supports the following optional arguments:

  • dbh

    Retrieve the data against a different database than the default one specified in static_class_info.

  • order_by

    Specify an ORDER BY clause to sort the objects returned.

    my $books = My::Model::Book->retrieve_list(
    	author_id => 12,
    	order_by  => 'books.name ASC',
    );
  • limit

    Limit the number of objects to return.

    # Get 10 books from author #12.
    my $books = My::Model::Book->retrieve_list(
    	author_id => 12,
    	limit     => 10,
    );
  • query_extensions

    Add joins and support different filtering criteria:

    • where_clauses

      An arrayref of clauses to add to WHERE.

    • where_values

      An arrayref of values corresponding to the clauses.

    • joins

      A string specifying JOIN statements.

    • joined_fields

      A string of extra fields to add to the SELECT.

    my $books = My::Model::Book->retrieve_list(
    	id               => [ 1, 2, 3 ],
    	query_extensions =>
    	{
    		where_clauses => [ 'authors.name = ?' ],
    		where_values  => [ [ 'Randal L. Schwartz' ] ],
    		joins         => 'INNER JOIN authors USING (author_id)',
    		joined_fields => 'authors.name AS _author_name',
    	}
    );
  • pagination

    Off by default. Paginate the results. You can control the pagination options by setting it to the following hashref:

    {
    	total_count => $total_count,
    	page        => $page,
    	page_max    => $page_max,
    	per_page    => $per_page,
    }

    Additionally, pagination can be set to '1' instead of {} and then the default options will be used.

  • lock (default 0)

    Add a lock to the rows retrieved.

    my $books = My::Model::Book->retrieve_list(
    	id   => [ 1, 2, 3 ],
    	lock => 1,
    );
  • allow_all (default 0)

    Retrieve all the rows in the table if no criteria is passed. Off by default to prevent retrieving large tables at once.

    # All the books!
    my $books = My::Model::Book->retrieve_list(
    	allow_all => 1,
    );
  • show_queries (default 0)

    Set to '1' to see in the logs the queries being performed.

    my $books = My::Model::Book->retrieve_list(
    	id           => [ 1, 2, 3 ],
    	show_queries => 1,
    );

reload()

Reload the content of the current object. This always skips the cache.

$book->reload();

flatten_object()

Return a hash with the requested key/value pairs based on the list of fields provided.

Note that non-native fields (starting with an underscore) are not allowed. It also protects sensitive fields.

#TODO: allow defining sensitive fields.

my $book_data = $book->flatten_object(
	[ 'name', 'isbn' ]
);

dump()

Return a Dumper( ) of the current object.

my $string = $book->dump();

ACCESSORS

id()

Return the value associated with the primary key for the current object.

my $id = $object->id();

is_verbose()

Return if verbosity is enabled.

This method supports two types of verbosity:

  • general verbosity

    Called with no argument, this returns whether code in general will be verbose.

    carp 'This is verbose'
    	if $class->is_verbose();
    carp 'This is verbose'
    	if $object->is_verbose();
  • verbosity for a specific type of operations

    Called with a specific type of operations as first argument, this returns whether that type of operations will be verbose.

    carp 'Describe cache operation'
    	if $class->is_verbose( $operation_type );
    carp 'Describe cache operation'
    	if $object->is_verbose( $operation_type );

    Currently, the following types of operations are supported:

    • 'cache_operations'

get_default_dbh()

Return the default database handle to use with this class.

my $default_dbh = $class->get_default_dbh();
my $default_dbh = $object->get_default_dbh();

get_memcache()

Return the memcache object to use with this class.

my $memcache = $class->get_memcache();
my $memcache = $object->get_memcache();

has_created_field()

Return a boolean to indicate whether the underlying table has a 'created' field.

my $has_created_field = $class->has_created_field();
my $has_created_field = $object->has_created_field();

has_modified_field()

Return a boolean to indicate whether the underlying table has a 'modified' field.

my $has_modified_field = $class->has_modified_field();
my $has_modified_field = $object->has_modified_field();

get_readonly_fields()

Return an arrayref of fields that cannot be modified via set(), update(), or insert().

my $readonly_fields = $class->get_readonly_fields();
my $readonly_fields = $object->get_readonly_fields();

get_unique_fields()

Return an arrayref of fields that are unique for the underlying table.

Important: this doesn't include the primary key name. To retrieve the name of the primary key, use $class-primary_key_name()>

my $unique_fields = $class->get_unique_fields();
my $unique_fields = $object->get_unique_fields();

get_table_name()

Returns the underlying table name for the current class or object.

my $table_name = $class->get_table_name();
my $table_name = $object->get_table_name();

get_primary_key_name()

Return the underlying primary key name for the current class or object.

my $primary_key_name = $class->get_primary_key_name();
my $primary_key_name = $object->get_primary_key_name();

get_object_cache_time()

Return the duration for which an object of the current class can be cached.

my $object_cache_time = $class->get_object_cache_time();
my $object_cache_time = $object->get_object_cache_time();

get_list_cache_time()

Return the duration for which a list of objects of the current class can be cached.

my $list_cache_time = $class->list_cache_time();
my $list_cache_time = $object->list_cache_time();

get_cache_key_field()

Return the name of the field that should be used in the cache key.

my $cache_time = $class->cache_key_field();
my $cache_time = $object->cache_key_field();

get_filtering_fields()

Returns the fields that can be used as filtering criteria in retrieve_list().

Notes:

  • Does not include the primary key.

  • Includes unique fields.

    my $filtering_fields = $class->get_filtering_fields();
    my $filtering_fields = $object->get_filtering_fields();

CACHE RELATED METHODS

invalidate_cached_object()

Invalidate the cached copies of the current object across all the unique keys this object can be referenced with.

$object->invalidate_cached_object();

get_object_cache_key()

Return the name of the cache key for an object or a class, given a field name on which a unique constraint exists and the corresponding value.

my $cache_key = $object->get_object_cache_key();
my $cache_key = $class->get_object_cache_key(
	unique_field => $unique_field,
	value        => $value,
);

retrieve_list_cache()

Dispatch of retrieve_list() when objects should be retrieved from the cache.

See retrieve_list() for the parameters this method accepts.

assert_dbh()

Assert that there is a database handle, either a specific one passed as first argument to this function (if defined) or the default one specified via static_class_info(), and return it.

my $dbh = $class->assert_dbh();
my $dbh = $object->assert_dbh();

my $dbh = $class->assert_dbh( $custom_dbh );
my $dbh = $object->assert_dbh( $custom_dbh );

cached_static_class_info()

Return a cached version of the information retrieved by static_class_info().

my $static_class_info = $class->cached_static_class_info();
my $static_class_info = $object->cached_static_class_info();

parse_filtering_criteria()

Helper function that takes a list of fields and converts them into where clauses and values that can be used by retrieve_list().

my ( $where_clauses, $where_values, $filtering_field_keys_passed ) =
	@{
		$class->parse_filtering_criteria(
			fields => \@field,
			values => \%value,
		)
	};

$filtering_field_keys_passed indicates whether %values had keys matching at least one element of @field. This allows detecting whether any filtering criteria was passed, even if the filtering criteria do not result in WHERE clauses being returned.

build_filtering_clause()

Create a filtering clause using the field, operator and values passed.

my ( $clause, $clause_values ) = $class->build_filtering_clause(
	field    => $field,
	operator => $operator,
	values   => $values,
);

get_cache()

Get a value from the cache.

my $value = $class->get_cache( key => $key );

set_cache()

Set a value into the cache.

$class->set_cache(
	key         => $key,
	value       => $value,
	expire_time => $expire_time,
);

delete_cache()

Delete a key from the cache.

my $value = $class->delete_cache( key => $key );

reorganize_non_native_fields()

When we retrieve fields via SELECT in retrieve_list_nocache(), by convention we use _[table_name]_[field_name] for fields that are not native to the underlying table that the object represents.

This method moves them to $object->{'_table_name'}->{'field_name'} for a cleaner organization inside the object.

$object->reorganize_non_native_fields();

INTERNAL METHODS

Those methods are used internally by DBIx::NinjaORM, you should not subclass them.

AUTHOR

Guillaume Aubert, <aubertg at cpan.org>.

BUGS

Please report any bugs or feature requests to bug-dbix-ninjaorm at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=DBIx-NinjaORM. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc DBIx::NinjaORM

You can also look for information at:

ACKNOWLEDGEMENTS

Thanks to ThinkGeek (http://www.thinkgeek.com/) and its corporate overlords at Geeknet (http://www.geek.net/), for footing the bill while I write code for them!

Thanks to Kate Kirby <<kate at cpan.org> > for her help with the design of this module, various additions, and pair programming retrieve_list_cache() with me.

Thanks to Jennifer Pinkham <<jpinkham at cpan.org > > for creating separate cache times for objects and arguments to IDs translations.

Thanks to Jamie McCarthy for adding the 'ignore' argument to insert().

COPYRIGHT & LICENSE

Copyright 2009-2012 Guillaume Aubert.

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/