Hacking Thy Fearful Symmetry

Hacker, hacker coding bright

New and Improved: DBIx-NoSQL-Store-Manager

January 5, 2018
perl dbix-nosql

A few years ago, I wanted some quick, no fuss way to serialize and persist data encapsulated as Moose objects -- a MooSQL database, if you will. In my research, I discovered DBIx::NoSQL and bolted my own DBIx::NoSQL::Store::Manager on top of it.

A few seconds ago, a new release of that latter module has been punted to CPAN. This update adds relationships between those objects. Now, I'm very conscious that I'm slowly creeping toward KiokuDB, but I think (well, whimsically hope) that I managed to balance dwim comfort and dark sorcery.

This being said, let's an example speak for itself.

The store itself

First up, the main Blog store class. That part is wonderfully boring.

package Blog;

use strict;
use warnings;

use Moose;

extends 'DBIx::NoSQL::Store::Manager';

__PACKAGE__->meta->make_immutable;

1;

The blog entry

Next we create the class that represents blog entries.

package Blog::Model::Entry;

use strict;
use warnings;

use Moose;

with 'DBIx::NoSQL::Store::Manager::Model';

__PACKAGE__->meta->make_immutable;

1;

A blog entry has a url (which is also its unique identifier).

...;
with 'DBIx::NoSQL::Store::Manager::Model';

has url => (
    traits   => [ 'StoreKey' ],
    is       => 'ro',
    required => 1,
);

__PACKAGE__->meta->make_immutable;
...;

And an author, which will also be an object saved in the database.

...;
has url => (
    traits   => [ 'StoreKey' ],
    is       => 'ro',
    required => 1,
);

has author => (
    traits       => [ 'StoreModel' ],
    cascade_save => 1,
    store_model  => 'Blog::Model::Author',
    is           => 'rw',
);

__PACKAGE__->meta->make_immutable;
...;

And can be assigned tags, also saved in the database.

We use two attributes because I want the saved tags to be in fact blog entry/tag pairs (so that's it's easy, say, to get all blog entries that have the tag perl), but don't want the end-user to have to worry about it.

...;
    store_model  => 'Blog::Model::Author',
    is           => 'rw',
);

has associated_tags => (
    traits        => [ 'Array', 'StoreModel' ],
    cascade_model => 1,
    store_model   => 'Blog::Model::Tag',
    is            => 'rw',
    default       => sub { [] },
);

has tags => (
    is      => 'ro',
    default => sub { [ map { $_->tag } $_[0]->associated_tags->@* ] },
    trigger => sub {
        my ( $self, $new ) = @_;
        $self->associated_tags( [ map { +{ entry => $self->store_key, tag => $_ } } @$new ] );
    },
);

__PACKAGE__->meta->make_immutable;
...;

Oh yeah, and content. Let's not forget some content...

...;
        $self->associated_tags( [ map { +{ entry => $self->store_key, tag => $_ } } @$new ] );
    },
);

has content => (
    is => 'rw',
);

__PACKAGE__->meta->make_immutable;
...;

The authors

We also need an Author class. Let's make it minimalistic: a name and an optional bio.

package Blog::Model::Author;

use strict;
use warnings;

use Moose;

with 'DBIx::NoSQL::Store::Manager::Model';

has name => (
    traits   => [ 'StoreKey' ],
    is       => 'ro',
    required => 1,
);

has bio => (
    is     => 'rw',
);

__PACKAGE__->meta->make_immutable;

1;

The tags

Same deal with the tags.

We want to be able to have the same tag associated with different blog entries, so we set the store key to be based on both the tag and the blog's id, and we index on those two values.

package Blog::Model::Tag;

use strict;
use warnings;

use Moose;

with 'DBIx::NoSQL::Store::Manager::Model';

has [qw/ tag entry /] => (
    traits   => [ 'StoreKey', 'StoreIndex' ],
    is       => 'ro',
    required => 1,
);

__PACKAGE__->meta->make_immutable;

1;

Using it

Setting the store is dead easy. Yes, even if the database didn't previously exist.

Love you, sqlite. Always did, always will.

use Blog;

my $store = Blog->connect( 'mystore.sqlite' );

You can create objects and put in the store....

my $auth = Blog::Model::Author->new(
    name => 'Bobby',
    bio  => 'Of Tablish fame',
);

$store->set( $auth );

... or do it all in one fell swoop.

my $author = $store->create( 'Author',
    name => 'Yanick',
    bio  => 'necrohacker',
);

Once created, it can be used as an attribute for another object.

$store->create( 'Entry',
    url    => '/first',
    author => $author,
);

# prints 'necrohacker'
say $store->get( 'Entry', '/first' )->author->bio;

Or if the object already exist in the db, just use its key.

$entry = $store->create( 'Entry',
    url    => '/with_id',
    author => 'Yanick',
);

# still prints 'necrohacker'
say $entry->author->bio;

The object doesn't exist yet? Pass in a hashref. It will be taken as the arguments of the attribute's object constructor.

$entry = $store->create( 'Entry',
    url    => '/with_id',
    author => { name => 'Yenzie', bio => 'twitterer' },
);

# prints 'twitterer'
$store->get( 'Author', 'Yenzie' )->bio;

Works with arrays of objects too.

$store->create( 'Entry', 
    url => '/with_tags',
    author => 'Yanick',
    tags => [ 'perl', 'moose' ],
);

$store->create( 'Entry', 
    url => '/also_with_tags',
    author => 'Yanick',
    tags => [ 'perl' ],
);

What does the search functionality looks like? Like this.

Right now the objects returned by the search aren't tied to the store, but it will be fixed soon.

# prints '/with_tags'
say $_->entry
    for $store->search(Tag => { tag => 'moose' } )->all;

Enjoy!

comments powered by Disqus

About the author

Yanick Champoux
Perl necrohacker , ACP writer, orchid lover. Slightly bonker all around. he/him/his