Mongoose::Cookbook - recipes, recipes
Here we go.
First connect to a database 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 multiple connections and databases are also supported.
This is a work in progress. Right now this syntax is supported:
# No class, this will be default db Mongoose->db( db_name=>'mydbone', ... ); # A db for one class Mongoose->db( class=>'Person', db_name=>'mydbone', ... ); # A db/host for several classes Mongoose->db( class=>['Address', 'Telephone'], db_name=>'mydbtwo', host=>'mongodb://host:27017' );
This is quite rudimentary, and will probably change in the future.
To quickly load your Mongoose classes (or any kind of package for that matter), use the load_schema method:
load_schema
package main; Mongoose->load_schema( search_path=>'MyApp::Schema', shorten=>1 );
Use it only once in your program. Your modules will be required and may be used from anywhere else.
require
If set to 1, the shorten option will alias MyApp::Schema::MyClass into MyClass for convenience.
shorten
MyApp::Schema::MyClass
MyClass
In case your class has attributes you don't want to store in the database.
package Person; use Moose; with 'Mongoose::Document'; has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); has 'age' => ( is => 'rw', isa => 'Int', default => 40 ); has 'salary' => ( is => 'rw', isa => 'Int', traits => ['DoNotMongoSerialize'] );
This can be accomplished several ways.
Use the ArrayRef Moose type.
ArrayRef
package Person; use Moose; with 'Mongoose::Document'; has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); has 'accounts' => ( is => 'rw', isa => 'ArrayRef[Account]' );
Then, define the Account class, either as a document or embedded document, depending on how you want it stored.
package Address; use Moose; with Mongoose::EmbeddedDocument; has 'amount' => ...;
But this has a memory and performance cost, since all related rows will be loaded in memory during object expansion.
To avoid loading related rows, use a Mongoose::Join parameterized type.
Establishing a Mongoose::Join relationship will load relationships lazily:
package Person; use Moose; with 'Mongoose::Document'; has 'name' => ( is => 'rw', isa => 'Str', required => 1 ); has 'accounts' => ( is => 'rw', isa => 'Mongoose::Join[Account]' );
Then retrieve data with a cursor:
my $large_acc = $person->accounts->find({ amount => { '$gt' => 100000 } }); while( my $account = $large_acc->next ) { ... }
Use Mongoose::Class instead of Moose.
Moose
package Article; use Mongoose::Class; with 'Mongoose::Document'; has title => ( is => 'rw', isa => 'Str', required => 1 ); has_one author => 'Author'; has_many comments => 'Comment'; belongs_to acc => 'Account';
Normalization is a relational concept, not natural to the document-oriented MongoDB, but an useful approach that should sometimes be taken into consideration.
Sometimes it may just be more adequate than storing relationships directly in objects:
package Authorship; use Mongoose::Class; with 'Mongoose::Document'; has_one 'author' => 'Author'; has_many 'articles' => 'Article'; # or even: # has_one 'article' => 'Article'; package main; # create my $authorship = Authorship->new; $authorship->author( Author->new ); $authorship->articles->add( Article->new ); $authorship->articles->add( Article->new ); # find my $articles = Authorship->find_one({ author=>$author->_id }); while( my $article = $articles->next ) { ... }
To make a long package name shorter, use:
package My::Mumbo::Jumbo::Class; with 'Mongoose::Document' => { -as => 'Mumbo', }; # then in your code my $obj = Mumbo->find_one({ flavor=>'gum' }) print ref $obj; # prints 'My::Mumbo::Jumbo::Class' print $obj->isa('Mumbo') # prints 1
In case you don't want a find_one or save method polluting your class.
find_one
save
package BankAccount; with 'Mongoose::Document' => { -alias => { 'find_one' => '_find_one' }, -excludes => { 'find_one' }, };
By default when collapsing data into the DB, Mongoose always sends to the MongoDB driver an unblessed version of your attribute.
But there are special types of objects that the Perl MongoDB driver may expect to receive blessed, such as a MongoDB::Code object.
MongoDB::Code
To skip the Mongoose Engine collapsing process altogether, use the Raw trait. That way, the MongoDB driver will receive the attribute as is.
Raw
has 'code' => ( is=>'rw', isa=>'MongoDB::Code', traits=>['Raw'] );
DateTime and DateTime::Tiny objects are passed untouched to the MongoDB driver, so using the Raw helper is not necesary.
This attribute:
has date => ( is => 'rw', isa => 'DateTime', default => sub{ DateTime->now } );
Is exactly the same as:
has date => ( is => 'rw', isa => 'DateTime', traits => ['Raw'], default => sub{ DateTime->now } );
Which in turn is stored as:
{ "_id" : ObjectId("4c750574a74100a588000000"), "date" : ISODate("2012-12-20T05:00:49Z") }
Either way the object will be expanded back from the database into a DateTime object.
DateTime
Just use MongoDB's query standard syntax:
query
# sorting my $sorted = Person->query( {}, { sort_by => { name => 1 } } )->each( sub { my $person = shift; print $person->name; } ); # paging my $paged = Person->query( {}, { sort_by => { name => 1 }, limit => 20, skip => 40 } )->each( sub { my $person = shift; print $person->name; } );
Support for the FileHandle Moose type is done making use of MooseDB::GridFS.
package Thing; use Mongoose::Class; with 'Mongoose::Document'; has 'file' => ( is=>'rw', isa=>'FileHandle' );
Then store it using a FileHandle type object:
FileHandle
require IO::File; my $fh = new IO::File "myfile.txt", "r"; my $t = Thing->new( file=>$fh ); $t->save;
The file is stored in Mongo's GridFS using the _id as filename. A special reference is created in your document to point to the GridFS file. The thing collection BSON may look like this:
_id
thing
{ "file": { "$ref": "FileHandle", "$id": ObjectId("4c7619d02f2e70d7a4140000") } }
When expanding the doc, the file attibute becomes a Mongoose::File, an extension of MongoDB::GridFS::File, which can be easily slurped or streamed.
my $file = Thing->find_one->file; print $file->isa('MongoDB::GridFS::File'); # prints 1
This is asymmetric, which means it's probably best if you check the attribute if it isa Mongoose::File before using it as such, specially in your class methods.
Mongoose::File
package Thing; # ... sub my_file_method { my $self = shift; if( $self->file->isa('Mongoose::File') ) { my $data = $file->slurp; # do stuff } else { # probably an IO::File filehandle still } }
This less-than-optimal asymmetric behavior may change in the future.
To skip Mongoose and have direct access to MongoDB, use the db and collection methods on your class:
db
collection
# finds and expands documents into objects my $cur = Person->find; # or just get the plain documents (hashrefs) my $cur = Person->collection->find; # get the MongoDB::Database object for your class my $db = Person->db; $db->run_command({ shutdown => 1 });
This can be useful for performance reasons, when you don't need to expand objects, or need to access MongoDB functionality not available in Mongoose. It's also useful for testing, to compare if some set of query results returned by Mongoose are identical to the straight MongoDB driver's results.
If you're passing around the _id attribute from your objects in, ie, a webapp, you're probably turning it into a string.
The MongoDB way of retrieving an _id from a string is a little annoying to type:
$author_id = "4dd77f4ebf4342d711000000"; $author = Author->find_one({ _id=>MongoDB::OID->new( value=>$author_id ) });
So, now there is a shorthand variation of find_one to this for you:
$author = Author->find_one( '4dd77f4ebf4342d711000000' );
Will automatically search by _id on the given value by first turning it into a MongoDB::OID.
MongoDB::OID
To install Mongoose, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Mongoose
CPAN shell
perl -MCPAN -e shell install Mongoose
For more information on module installation, please visit the detailed CPAN module installation guide.