=pod
=head1 NAME
Mongoose::Cookbook - recipes, recipes
=head1 VERSION
version 0.20
=head1 RECIPES
Here we go.
=head2 Connecting to MongoDB
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.
=head2 Connecting to more than one database
This is a work in progress. Right now this syntax is supported:
Mongoose->db( class=>'Person', db_name=>'mydbone', ... );
Mongoose->db( class=>'Address', db_name=>'mydbtwo', ... );
This is quite rudimentary, and will probably change in the future.
=head2 Loading a Schema
To quickly load your Mongoose classes (or any kind of
package for that matter), use the C<load_schema> method:
package main;
Mongoose->load_schema( search_path=>'MyApp::Schema', shorten=>1 );
Use it only once in your program. Your modules will be C<require>d
and may be used from anywhere else.
If set to 1, the C<shorten> option will alias C<MyApp::Schema::MyClass>
into C<MyClass> for convenience.
=head2 Preventing attributes from being stored
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'] );
=head2 One-to-many Relationships
This can be accomplished several ways.
=head3 ArrayRef
Use the C<ArrayRef> Moose type.
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 L<Mongoose::Join>
parameterized type.
=head3 Mongoose::Join
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 ) {
...
}
=head2 Sugar for Defining Relationships
Use L<Mongoose::Class> instead of C<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';
=head2 Normalizing a Relationship
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 ) {
...
}
=head2 Package aliasing
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
=head2 Method aliasing
In case you don't want a C<find_one> or C<save> method
polluting your class.
package BankAccount;
with 'Mongoose::Document' => {
-alias => { 'find_one' => '_find_one' },
-excludes => { 'find_one' },
};
=head2 Raw Storage of Attribute Values
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 C<DateTime> object.
To skip the Mongoose Engine collapsing process
altogether, use the C<Raw> trait. That way,
the MongoDB driver will receive the attribute
as is.
has 'date' => ( is=>'rw', isa=>'DateTime', traits=>['Raw'] );
=head2 DateTime Handling
By default, DateTime objects are unblessed and collapsed
by Mongoose.
This attribute:
has 'date' => (
is=>'rw',
isa=>'DateTime',
default=>sub{ DateTime->now }
);
Becomes a rather humongous:
{ "_id" : ObjectId("4c75049ca741006788000000"),
"date" : {
"local_rd_secs" : { "floatApprox" : 42908 },
"local_rd_days" : { "floatApprox" : 734009 },
"rd_nanosecs" : { "floatApprox" : 0 },
"utc_rd_secs" : { "floatApprox" : 42908 },
"local_c" : { "hour" : { "floatApprox" : 11 },
"second" : { "floatApprox" : 8 },
"month" : { "floatApprox" : 8 },
"quarter" : { "floatApprox" : 3 },
"day_of_year" : { "floatApprox" : 237 },
"day_of_quarter" : { "floatApprox" : 56 },
"minute" : { "floatApprox" : 55 },
"day" : { "floatApprox" : 25 },
"day_of_week" : { "floatApprox" : 3 },
"year" : { "floatApprox" : 2010 } },
"formatter" : null,
"utc_year" : { "floatApprox" : 2011 },
"offset_modifier" : { "floatApprox" : 0 },
"utc_rd_days" : { "floatApprox" : 734009 }
}
}
The MongoDB driver provides a much leaner implementation
for DateTime, but that requires you to tell Mongoose that
the attribute value should go unblessed:
has 'date' => (
is=>'rw',
isa=>'DateTime',
traits=>['Raw'],
default=>sub{ DateTime->now }
);
Which in turn is stored as a much more acceptable:
{ "_id" : ObjectId("4c750574a74100a588000000"),
"date" : "Wed Aug 25 2010 13:58:44 GMT+0200 (CEST)" }
Either way the object will be expanded back from the database
into a C<DateTime> object.
=head2 Paging and Sorting
Just use MongoDB's C<query> standard syntax:
# 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;
}
);
=head2 FileHandle
Support for the FileHandle Moose type is done making use
of L<MooseDB::GridFS>.
package Thing;
use Mongoose::Class; with 'Mongoose::Document';
has 'file' => ( is=>'rw', isa=>'FileHandle' );
Then store it using a C<FileHandle> type object:
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
C<_id> as filename. A special reference is created in
your document to point to the GridFS file. The C<thing>
collection BSON may look like this:
{ "file":
{ "$ref": "FileHandle",
"$id": ObjectId("4c7619d02f2e70d7a4140000") }
}
When expanding the doc, the file attibute becomes
a L<Mongoose::File>, an extension of L<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 C<Mongoose::File>
before using it as such, specially in your class methods.
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.
=head2 Direct access to MongoDB Calls
To skip Mongoose and have direct access to MongoDB, use the
C<db> and C<collection> methods on your class:
# 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.
=head2 Finding by string _id
If you're passing around the C<_id> attribute from your
objects in, ie, a webapp, you're probably
turning it into a string.
The MongoDB way of retrieving an C<_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 C<find_one> to this for you:
$author = Author->find_one( '4dd77f4ebf4342d711000000' );
Will automatically search by C<_id> on the given value by first
turning it into a C<MongoDB::OID>.
=cut