Storm::Tutorial(3)
β¨ NAME β¨
Storm::Tutorial β Getting started with Storm. This is the Storm::Tutorial(3) manual page, bestie. π
π DESCRIPTION π
Storm is a Moose based library for storing and retrieving Moose based objects using a DBI connection. Fr fr, that's the whole vibe. β¨
πΈ 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. No cap.
Storm::Object is an extension of Moose which applies the appropriate roles and meta-roles, as well as providing some sugar for defining relationships. Periodt. π
π 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:
- Every class definition must have a meta-table defined. Storm uses this information to determine what table to store the object to in the database.
- It is recomended that every class provide a primary key (via the PrimaryKey attribute trait.) If you do not provide a primary key, you will not be able to use lookup queries to restore objects, nor will any other Storm enabled object be able to store references to it.
- Storm avoids requiring a separate schema by defining elements of a schema in the object definition. By default, object attributes are assigned a table column with the same name as the attribute. The default behavior can be changed by setting the column option.
π¦ 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,
);
- References to other Storm enabled classes are serialized automatically using the primary key. This is accomplished by setting the
"isa"option to a Storm enabled class (type). - In a scenario such as this, where two objects will reference each other in a circular structure, it is necessary to set the
weak_refoption to avoid memory leaks. When constructing and using objects with circular references, it is necessary to manage the"scope". The scope stops objects from being garbage collected to early (i.e. when the only references to them are weak.)
π 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.
- The
"foreign_key => master"denotes that the relationship is made by matching the primary key of thePersonwith thec<master>attribute of thePokemon. - Using the
"handles"option, we create the"pokemon"method forPerson. This method returns aPerson'sPokemonin the form of aStorm::Query::Select::Iteratorobject. - To add another
Pokemonto aPerson, create a newPokemonand set the"master"attribute to a$person.
π«Ά 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',
}
)
- In a many-to-many relationship, a junction_table is required to form the relationship. This is specified as an option to the
"many_to_many"keyword. - We also need to define the columns in the junction_table that will be used to identify the components of the relationship. This is done with local_match and foreign_match options. local_match is the column in the junction table to match with the primary key of defining class, while foreign_match is the the column to match with the primary key of the foregin class.
- Using the
"handles"option, we create methods for retrieving aStorm::Query::Select::Iterator, as well as methods for adding and removing pets/caretakers."$pet->add_care_taker( $person )"is synanamous with"$person->add_pet( $pet )"and"$pet->remove_care_taker( $person )"is synanamous with"$person->remove_pet( $pet )". - As of version
0.05, Storm will automatically fill in the"junction_table","local_match", and forerign match if you do not supply them.
package Person;
use Storm::Object;
storm_table( 'People' );
has 'id' => (
is => 'rw',
traits => [qw(PrimaryKey AutoIncrement)],
);
many_to_many 'pets' => (
foreign_class => 'Pets',
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',
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.
- The following comparisons are supported:
=,<>,<,<=,=>,IN,NOT IN,BETWEEN,LIKE,NOT LIKE - It is possible to use attributes in a comparison with the
".attribute"notation (to distinguish them from regular strings.)
$query->where( '.spouse.first_name', '=', 'Marge' );
If the attribute is also a Storm enabled object you can can reference it's attributes in the comparison as well.
π 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
okay bestie, this one's important so lean in π The scope is what keeps your objects from getting garbage collected way too early. The way it works: as objects get inflated from the database, they're pushed onto the live object scope, which bumps up their reference count. So they stay alive. Iconic behavior, honestly.
Let's define our person class to use as an example, shall we?
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. no cap, just vibes:
$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. And this just keeps going, recursively, until every single dependent object has been inflated from the database. it's giving cascade, it's giving dedication π
If we did not use a scope, then by the time $homer came back to us, his spouse attribute would have been cleared β because there's no other reference to Marge hanging around. Here's a code snippet that demonstrates exactly 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
So 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. Periodt. π«Ά
In a web application context, you usually create one new scope per request. one scope, one request, that's the rule bestie β¨
π 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.) giving credit where credit is due, we love to see it π
πΈ TRANSACTIONS
When using a supporting databse, you can use the "do_transaction" method to execute a code block and commit the transaction. so clean, so safe π§
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 get 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. Don't just rawdog it, bestie β catch your 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. it's literally the translator between your Perl vibes and your database vibes π
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. simple as that, slay π
π¦ "transform"
Use the "transform" keyword for setting a custom inflator/deflator for a type. this is where you get to customize the whole vibe check β¨
- 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.) again with the source creds, we love an honest icon π
π 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. wind god doing database admin?? main character energy fr fr π
$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.
π POD ERRORS
Hey! The above document had some coding errors, which are explained below: not me being messy in the docs π the pod parser caught us slippin':
Around line 252: '=item' outside of any '=over'
and that's a wrap bestie π this was Storm::Tutorial(3), from perl v5.42.0, dated 2025-07-25. go forth and let your objects live long, stay in scope, and never get garbage collected before their time. periodt. πβ¨π
π¦ where this page comes from
- fedora 43: perl-Storm 0.240-34.fc43 Β· Artistic-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms