NAME

Meerkat::Cookbook - Meerkat recipes with do's and don'ts

VERSION

version 0.016

COOKBOOK

Preventing attributes from being serialized

If you have private attributes that should not be serialized — particularly lazy ones that can be reconstructed on demand — add the DoNotSerialize trait from MooseX::Storage:

    use MooseX::AttributeShortcuts; # 'lazy'
    use MooseX::Storage;

    has big_object => (
        is => 'lazy',
        isa => 'Heavy::Object',
        traits => ['DoNotSerialize'],
    );

    sub _build_big_object { ... }

Custom collection classes

You can extend Meerkat::Collection and add additional methods for things like common queries or collection maintenance. Here is a custom collection class that adds a query method:

    package MyCollection::Person;

    use Moose 2;
    extends 'Meerkat::Collection';

    sub find_by_name {
        my ( $self, $name ) = @_;
        return $self->find_one( { name => $name } );
    }

To use custom collection classes, put them under a different namespace from your model classes, and pass that as the collection_namespace parameter when creating a Meerkat object:

    my $meerkat = Meerkat->new(
        model_namespace      => "My::Model",
        collection_namespace => "MyCollection",
        database_name        => "test",
    );

Then, collection requests will use MyCollection::* or will fall back to Meerkat::Collection:

    my $person = $meerkat->collection( "Person" ); # MyCollection::Person
    my $other  = $meerkat->collection( "Other" );  # Meerkat::Collection

Date objects

Do you really need them in your model? MongoDB does have support for inflating and deflating DateTime and DateTime:Tiny objects, but do you really want the overhead of doing so each time?

The simplest and sanest thing to do, in my opinion, is to keep your times in either epoch seconds or some standardized format like ISO 8601, inflate them when you need to work with them, and store any changes to them in the same format.

    use MooseX::Types::ISO8601 qw/ISO8601Date/

    has birthday => (
        is => 'ro',
        isa => ISO8601Date,
        coerce => 1,
    );

If you try to store DateTime or DateTime::Tiny objects directly, the MongoDB client will translate them to its internal datetime format. Meerkat sets the MongoDB dt_type option to undef, so the MongoDB client will always return epoch seconds for its internal datetime type. With Meerkat, objects go in, but numbers come back out.

However, if you aren't consistent about how you store datetimes, you run the risk of getting mixed numbers and internal datetime types in your documents and that's probably a bad idea. Either store as epoch seconds or a standard format or store objects, but don't mix them up.

To make things a little easier at the cost of some complexity, Meerkat offers Meerkat::DateTime and the MeerkatDateTime type.

    use Meerkat::Types qw/MeerkatDateTime/;

    has birthday => (
        is => 'ro',
        isa => MeerkatDateTime,
        coerce => 1,
    );

For an attribute of type MeerkatDateTime, initializing it with epoch seconds, a DateTime object or a DateTime::Tiny object will coerce into a Meerkat::DateTime object.

    $person->create( name => "Joe", birthday => $when );
    # birthday is coerced from $when to a Meerkat::DateTime object

This object holds epoch seconds and will lazily inflate a DateTime object for you on demand:

    my $dt = $obj->birthday->DateTime;

Again, because MongoDB will store a DateTime or DateTime::Tiny object with its internal format, you can then update fields using objects of those types and MongoDB will convert them to an internal type when storing and return them as epoch seconds, which then get coerced back into a Meerkat::DateTime object when the update is synchronized:

    $obj->update_set( birthday => $new_datetime_obj );
    # birthday winds up an updated Meerkat::DateTime object.

This is all a little convoluted but might save a little coding.

Ultimately, if you want a consistent datetime field type in the database, your options are pretty straightforward:

  • Option 1: store only epoch seconds or other standard format and inflate them to DateTime yourself

  • Option 2: store only epoch seconds and let the attribute coerce to Meerkat::DateTime objects

  • Option 3: store only DateTime(::Tiny) objects and let the attribute coerce them to Meerkat::DateTime objects

What you don't want to do is mix storing DateTime objects and raw epoch seconds or any other format.

Note that Meerkat::DateTime objects only work when your attribute has a MeerkatDateTime type constraint on it. You can't just store Meerkat::DateTime objects into any random field or deep data structure and expect things to work.

Embedded objects not really supported

Meerkat offers no real support for embedded objects.

Again, do you really want this? If your objects are read-only, it might be OK, but if not, you'll be tempted to modify the data in the embedded object and then it will be out of sync with the database and you're screwed.

If you really want to experiment with this, if an embedded object does the MooseX::Storage role, then it will be packed into the database on object creation as a hash reference following the MooseX::Storage format.

But if you update the field, you have to pack the replacement object yourself.

    $obj->update_set( embedded => $new_embedded->pack );

This has not been tested and is all theoretical. Consider yourself suitably warned.

Handling deserialization errors

If for any reason, a document in the database contains data that can't be validated against attribute type and BUILD constraints, Meerkat will throw an exception.

This won't happen during create because the new object has just been validated and is being newly inserted, but it could happen during either update or sync if the document in the database (possibly post-update) fails validation.

If you catch the exception, your object will not have been modified by the changes in the database and thus, by definition, contains a valid document.

One rather crude attempt to fix it would be to force a reinsert. However, this is destructive so use with extreme caution. You probably want to find out why the document got corrupted and fix that rather than just jamming the document back in and hoping for the best.

OTHER QUESTIONS?

If you have other ideas or questions for the cookbook, please contact the author.

AUTHOR

David Golden <dagolden@cpan.org>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2013 by David Golden.

This is free software, licensed under:

  The Apache License, Version 2.0, January 2004