NAME

Mongoose::Intro - an introduction

VERSION

version 0.01_01

MOTIVATION

This module is an attempt to bring together the full power of Moose into one of the hottest new databases out there: MongoDB.

Before using this module you should take a little time to read on MongoDB.

Why not use KiokuDB?

KiokuDB is an awesome module that maps objects to data and caters to a wide variety of backends. Currently there's even a MongoDB backend that may suit your needs.

So, why use Mongoose instead?

  • You want your objects to have their own collection. KiokuDB stores all objects in a single collection. MongoDB performs best the more collections you have.

  • You want to be able to store relations as either embedded documents or foreign documents. KiokuDB embeds everything. Here you get to choose.

  • You want to abstract your data from your class representation. KiokuDB stores an extra field called __CLASS__ that ties data to its representation. It's not a bad decision, it's just a design choice.

  • You don't need to keep a scope. KiokuDB will keep objects in scope for you, avoiding breakage in relationships. Here nothing is kept in scope.

  • You don't plan to use transactions. KiokuDB is transaction ready. But MongoDB is not. So, what's the point here?

  • You feel adventurous.

If you don't need any of this, go grab KiokuDB instead. It's much more configurable, stable and you get the option to switch backends in the future.

REQUIREMENTS

To use this module, you need:

MongoDB installed somewhere in your network.

Grab a pre-built copy for your OS from here, or build it from sources.

The MongoDB Perl driver

cpan MongoDB
cpan Mongoose

Moose classes

package MyClass;
use Moose;
with 'Mongoose::Document';
has 'yada' => ( is=>'rw', isa=>'Str' );

FEATURES

Some of Mongoose features:

  • It's fast. Not as fast as working with MongoDB documents directly though. But it's way faster than any other ORM and relation-based mapping modules out there.

  • * It handles most object relationships, circular references included.

  • No persistency. It doesn't manage states for your object. If you save your object twice, it writes twice to the database. In most cases, this is actually faster than trying to manage states.

  • Primary keys. This is quite a extraneuos concept for objects, and it's not mandatory. But it allows you to automatically control when new objects translate to new MongoDB documents, or just update them.

    This is an experimental feature. There are other ways to do this anyway with the MongoDB built-in _id primary-key attribute.

  • Schema-less data. MongoDB does not hold a schema. You can create new attributes for your object and delete old ones at your leasure.

  • No data-object binding means that you may reuse collections, and peruse inheritance to great extent.

CAVEATS

  • This is very much *BETA* software. In fact it's almost alpha, except that the API is so simple it will probably not change, so let's call it "beta".

  • This module intrusively imports singleton based methods into your class. It's the price to pay for a simpler user interface and less keystrokes.

  • Object expansion from the database is done using plain bless most of the time. Which means your attribute triggers, etc. will not be fired during expansion. There are exceptions to this rule though.

  • After saving or loading objects from the database, your object will have an extra attribute, _id. This is a unique identifier. The _id value can be overwritten if you wish.

GETTING STARTED

There are only two steps to start using Mongoose:

1) Create at least one class that consumes a Mongoose::Document tole. 2) Connect to a Mongo database.

MongoDB does not require you to previously create a database, a collection or a document schema for your collection. This is done on the fly for you.

To make your Moose classes "Mongoable", all they need is to consume either one of two roles: Mongoose::Document or Mongoose::EmbeddedDocument.

Turning your classes into Mongo Documents

The difference between these roles lies in the way objects will be later stored and loaded from the DB.

Read the MongoDB docs if you don't understand the difference.

Document

Akin to a row in the relational model. Relationships are stored using MongoDB's foreign key system.

EmbbededDocument

Tells Mongoose to store your class as an embedded document, part of a parent document.

This is usually faster than using document-to-document relations. But it's not meant for object reuse by foreign documents.

Methods you get when using the Document roles

Both Document and EmbeddedDocument will import into your class the following methods:

save

Saves the current object to the database, inserting the document if needed.

$person->save;

delete

Deletes the correspondind document from the database.

$person->delete;

find

Wraps MongoDB's find method to return a cursor that expands data into objects.

query

my $cursor = Person->query({ age => { '$lt' => 30 } });

find_one

Finds exactly one document.

my $jack = Person->find_one({ first_name => 'Jack' });

collection

Returns the MongoDB::Collection object supporting this class. It's a way to switch quickly back to MongoDB hash documents.

Person->find_one({ name=>'thyself' }); # isa Person

# whereas

Person->collection->find_one({ name=>'thyself' }); # ref = HASH

Connecting

Then connect to a database anywhere before starting to use your classes.

use Mongoose;
Mongoose->db( 'mydb'); # looks for a localhost connection

# or, for more control:

Mongoose->db(
	host=>'mongodb://data.server:4000',
	db_name=>'mydb'
);           

This is done globally here for simplicity sake, but a more object oriented notation is also supported.

Parameterization

Mongoose roles are MooseX::Role::Parameterized for greater flexibility.

Collection naming

You can control the collection name for an individual class this way:

package My::Mumbo::Jumbo::Class;
use Moose;
with 'Mongoose::Document' => {
	-collection_name => 'mumbo_jumbo'	
};

Global collection naming stategy

By default, Mongoose will turn package names into collections this way:

Package name          | Collection name
----------------------+---------------------
Person                | person
Humpty::Dumpty        | humpty_dumpty
HumptyDumpty          | humpty_dumpty
MyApp::Schema::Jumbo  | myapp_schema_jumbo

You can change this standard anytime, by setting the Mongoose-naming> closure:

# remove prefix and return
#  a lower case collection name
Mongoose->naming( sub{
	my $pkg = shift;
	$pkg =~ s{^MyApp::Schema::}{}g;
	return lc $pkg;
});

Primary keys

The standard way MongoDB deals with primary keys is by using the _id attribute. By default, a MongoDB::OID is assigned to each object you commit to the database with save.

Checkout this Devel::REPL example:

$ re.pl
> use Person;
> my $hurley = Person->new(name=>'Hurley');                                                                                                                                         
$Person1 = Person=HASH(0x102099d08);
> $hurley->dump;                                                                                                                                                                    
$VAR1 = bless( {
				 'name' => 'Hurley'
			   }, 'Person' );
> $hurley->save;                                                                                                                                                                    
4c683525a74100a8df000000                                                                                                                                                            $ $hurley->dump;         
> $hurley->dump;                                                                                                                                                                    
$VAR1 = bless( {
				 '_id' => bless( {
								   'value' => '4c683525a74100a8df000000'
								 }, 'MongoDB::OID' ),
				 'name' => 'Hurley'
			   }, 'Person' );

This is pretty standard MongoDB stuff.

Now, for a more control over your primary key, use the role parameter -pk.

package BankAccount;
with 'Mongoose::Document' => {
	-pk    => [qw/ drivers_license /]
};
has 'drivers_license' => (is=>'rw', isa=>'Int' );

That way, with every insert or update, the drivers_license field is checked with the help of the upsert feature of Mongo.