Jeffrey Ray Hallock > Storm-0.200 > Storm::Tutorial

Download:
Storm-0.200.tar.gz

Annotate this POD

CPAN RT

New  1
Open  1
View/Report Bugs
Source   Latest Release: Storm-0.240

NAME ^

Storm::Tutorial - Getting started with Storm

DESCRIPTION ^

Storm is a Moose based library for storing and retrieving Moose based objects using a DBI connection.

CONNECTING ^

Storm connects to databases using the uqiquitous DBI module. Database handles are spawned using a Storm::Source object which holds connection information. In the example below, the Storm::Source object is coerced from the arguments passed to the Storm constructor.

 use Storm;

 $storm->new(
    source => ['DBI:mysql:timsdev:10.0.1.11:3306', 'user', 'pass']
 );

BUILDING ^

Storm is for storing Moose based objects. It is required that the objects have the Storm roles and meta-roles applied.

Storm::Object is an extension of Moose which applies the appropriate roles and meta-roles, as well as providing some sugar for defining relationships.

Simple example

 package Person;
 use Storm::Object;
 storm_table( 'Person' );

 has 'id' => (
     is => 'rw',
     traits => [qw(PrimaryKey AutoIncrement)],
 );

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

 has 'dob' => (
     is => 'rw',
     column => 'birth_date',
 );

This is a very simple example, but demonstrates a few key concepts:

Circular references

 package Person;
 use Storm::Object;
 storm_table( 'People' );

 has 'id' => (
     is => 'rw',
     traits => [qw(PrimaryKey AutoIncrement)],
 );

 has 'spouse' => (
     is => 'rw',
     isa => 'Person',
     weak_ref => 1,
 );

RELATIONSHIPS ^

Relationships are devised in two ways. We demonstrated one manner in the example above by setting an attributes isa option to a Storm enabled class. This allows you to referance a singular object. Here we will demonstrate making one-to-many and many-to-many relationships using the has_many keyword.

One-to-many

 package Person;
 use Storm::Object;
 storm_table( 'People' );

 has 'id' => (
     is => 'rw',
     traits => [qw(PrimaryKey AutoIncrement)],
 );

 one_to_many 'pokemon' => (
    foreign_class => 'Pokemon',
    match_on => 'master',
    handles => {
        pokemon => 'iter',
    }
 );

 package Pokemon;
 use Storm::Object;
 storm_->table( 'Pokemon' ); 

 has 'id' => (
     is => 'rw',
     traits => [qw(PrimaryKey AutoIncrement)],
 );

 has 'master' => (
     is => 'rw',
     isa => 'Person',
     weak_ref => 1,
 );

Here, we define the components of a relationship between the Person class and the Pokemon class uing the one_to_many keyword.

Many-to-many

 package Person;
 use Storm::Object;
 storm_table( 'People' );

 has 'id' => (
     is => 'rw',
     traits => [qw(PrimaryKey AutoIncrement)],
 );

 many_to_many 'pets' => (
     foreign_class => 'Pets',
     junction_table => 'PeoplesPets',
     local_match => 'person',
     foreign_match => 'pet',
     handles => {
         parents => 'iter',
         add_pet => 'add',
         remove_pet => 'remove',
     }
 )

 package Pet;
 use Storm::Object;
 storm_table( 'Pets' );

 has 'id' => (
     is => 'rw',
     traits => [qw(PrimaryKey AutoIncrement)],
 );

 many_to_many 'care_takers' => (
     foreign_class => 'Pets',
     junction_table => 'PeoplesPets',
     local_match => 'person',
     foreign_match => 'pets',
     handles => {
         care_takers => 'iter',
         add_care_taker => 'add',
         remove_care_taker => 'remove',
     }
 )

CRUD ^

Storm provides queries for the four basic data operations Create, Read, Update, and Delete (CRUD) as well as a select query for searching.

Insert

 $storm->insert( @objects );

Inserts @objects into the database. Objects may onle be inserted if they do not already exist in the database. An error will be thrown if you try to insert an object that already exists. An error will also be thrown if the object has a primary key and it is undef (unless using the AutoIncrement trait.)

Lookup

 $storm->lookup( $class, @object_ids );

Retrieves object from the database by primary key. The $class attribute is required so Storm knows where to find and how to inflate the objects. If any of the object's attributes reference other Storm enabled objects, they will be looked up and inflated as well. This will continue until all dependent object have been retrieved and inflated.

Update

 $storm->update( @objects );

Updates the state of the @objects in the database. If you try to call update on an object that is not already in the database, an error will be thrown. Only the @objects passed to update will be affected, any Storm enabled objects they reference will not updated in the database. You must call update on them yourself.

Delete

 $storm->delete( @objects );

Deletes the @objects from the database. The local references to them will still exists until you destroy them or they go out of scope.

SELECT ^

Searching is possible using a select query. The select query is a little more complex than it's counterparts.

Iterators

 $query = $storm->select( 'Person' );
 $iter = $query->results;

 while ( $object = $iter->next ) {

    ... do stuff with $object ...

 }

Calling the results method on a select query returns a Storm::Query::Select::Iterator for iterating over the result set.

Where

 $query = $storm->select( 'Person' );
 $query->where( '.last_name', '=', 'Simpson' );
 $query->where( '.age', '>', 10 );
 $iter = $query->results;

Use Storm::Query::Select's where method to select specific objects.

Placeholders

 $query->where( '.age', '>', '?' );
 $iter = $query->results( 10 );

You can use a ? as placeholder. Supply the arguments to replace the placeholders when calling the results method.

Order-by

 $query->order_by( '.lastname', '.age DESC' );

Use the order_by method to sort the results.

SCOPE ^

The scope ensures that objects aren't garbage collected to early. As objects are inflated from the database, the are pushed onto the live object scope, increasing their reference count.

Let's define out person class to use as an example.

 package Person;
 use Storm::Object;

 has 'id' => (
     is => 'rw',
     traits => [qw(PrimaryKey AutoIncrement)],
 );

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

 has 'spouse' => (
     is => 'rw',
     isa => 'Person',
     weak_ref => 1,
 );

Now, insert some objects into the database.

     $storm->insert(
        Person->new( name = 'Homer' ),
        Person->new( name = 'Marge' )
     );

And then we can link them together:

 {
     my $scope = $storm->new_scope;

     my ( $homer, $marge ) = $storm->lookup( $homer_id, $marge_id );
     $homer->spouse( $marge );
     $marge->spouse( $homer );
     $storm->update( $homer, $marge );
 }

Now we can we can load the objects from the database like this:

 {
     my $scope = $storm->new_scope;

     my $homer = $storm->lookup( $homer_id );

     print $homer->spouse->name; # Marge
 }

 {
     my $scope = $storm->new_scope;

     my $marge = $storm->lookup( $marge_id ); 

     print $marge->spouse->name; # Homer Simpson

     refaddr( $marge ) == refaddr( $marge->spouse->spouse ); # true
 }

When the initial object is loaded, all the objects that the initial object depends on will be loaded. This will continue until all dependent objects have been inflated from the database.

If we did not use a scope, by the time $homer his spouse attribute would have been cleared because there is no other reference to Marge. Here is a code snippet that demonstrates why:

 sub get_homer {
     my $homer = Person->new( name => 'Homer' );
     my $marge = Person->new( name => 'Marge' ); 

     $homer->spouse( $marge );
     $marge->spouse( $homer );

     return $homer;

    # at this point $homer and $marge go out of scope
    # $homer has a refcount of 1 because it's the return value
    # $marge has a refcount of 0, and gets destroyed
    # the weak reference in $homer->spouse is cleared
 }

 my $homer = get_homer();

 $homer->spouse; # this returns undef

By using this idiom:

 {
    my $scope = Storm->new_scope;

    ... do all Storm work in here ...
 }

You are ensuring that the objects live at least as long as necessary.

In a web application context, you usually create one new scope per request.

Credit

The live object scope was largely inspired by the KiokuDB module. Some of the code and documentation for this functionality was taken directly from the KiokuDB source (and possibly modified.)

TRANSACTIONS ^

When using a supporting databse, you can use the do_transaction method to execute a code block and commit the transaction.

 eval {
    $storm->do_transaction( sub {

        ... do work on $storm ...

    });
 }

 print $@ if $@; # prints error

The transaction will only be committed if they block executes successfully. If any exceptions are thrown, the transaction will be rolled back. It is recommended that you execute the transaction inside an eval block to trap any errors that are thrown. Alternatively, you can use a module like TryCatch or Try::Tiny to trap errors.

POLICY ^

The policy is used to determine what data type is used by the DBMS to store a value. The policy also determines how different types of values are inflated/deflated.

 package My::Policy;
 use Storm::Policy;

 define 'DateTime', 'DATETIME';

 transform 'DateTime',
    inflate { DateTime::Form::SQLite->parse_datetime( $_ ) },
    deflate { DateTime::Form::SQLite->format_datetime( $_ ) };


 package main;
 use Storm;

 $storm->new( source => ..., policy => 'My::Policy' );
define

Use the define keyword to determine what data type the DBMS should used to store a value of the given type. In this case we want DateTime objects to be stored in the database using the DATETIME data type.

transform

Use the transform keyword for setting a custom inflator/deflator for a type.

The inflator is defined using the inflate keyword. The $_ special variable will be set to the value to be inflated. The inflator is expected to return the inflated value.

The deflator is defined using the deflate keyword. The $_ special variable will be set to the value to be deflated. The deflator is expected to return the deflated value.

Credit

The policy was inspired by the Fey::ORM module. Some of the code this functionality was taken directly from theFey::ORM source (and possibly modified.)

AEOLUS ^

Aeolus is the greek god of the wind. Aeolus helps manage your database installation. With Aeolus you can easily install and remove the tables your classes need to store their data.

 $storm->aeolus->install_class( 'Person' );

See Storm::Aeolus for more information.

AUTHORS ^

Jeffrey Ray Hallock <jeffrey.hallock at gmail dot com>

Dave Rolsky <autarch@urth.org>

Yuval Kogman <nothingmuch@woobling.org>

COPYRIGHT ^

    Copyright (c) 2010-2011 Jeffrey Ray Hallock.

    Copyright (c) 2010-2011 Dave Rolsky.

    Copyright (c) 2008, 2009 Yuval Kogman, Infinity Interactive.

    All rights reserved. This program is free software; you can redistribute it
    and/or modify it under the same terms as Perl itself.
syntax highlighting: