1Storm::Tutorial(3) User Contributed Perl Documentation Storm::Tutorial(3)
2
3
4
6 Storm::Tutorial - Getting started with Storm
7
9 Storm is a Moose based library for storing and retrieving Moose based
10 objects using a DBI connection.
11
13 Storm connects to databases using the uqiquitous DBI module. Database
14 handles are spawned using a Storm::Source object which holds connection
15 information. In the example below, the Storm::Source object is coerced
16 from the arguments passed to the Storm constructor.
17
18 use Storm;
19
20 $storm->new(
21 source => ['DBI:mysql:timsdev:10.0.1.11:3306', 'user', 'pass']
22 );
23
25 Storm is for storing Moose based objects. It is required that the
26 objects have the Storm roles and meta-roles applied.
27
28 Storm::Object is an extension of Moose which applies the appropriate
29 roles and meta-roles, as well as providing some sugar for defining
30 relationships.
31
32 Simple example
33 package Person;
34 use Storm::Object;
35 storm_table( 'Person' );
36
37 has 'id' => (
38 is => 'rw',
39 traits => [qw(PrimaryKey AutoIncrement)],
40 );
41
42 has 'name' => (
43 is => 'rw',
44 );
45
46 has 'dob' => (
47 is => 'rw',
48 column => 'birth_date',
49 );
50
51 This is a very simple example, but demonstrates a few key concepts:
52
53 • Every class definition must have a meta-table defined. Storm uses
54 this information to determine what table to store the object to in
55 the database.
56
57 • It is recomended that every class provide a primary key (via the
58 PrimaryKey attribute trait.) If you do not provide a primary key,
59 you will not be able to use lookup queries to restore objects, nor
60 will any other Storm enabled object be able to store references to
61 it.
62
63 • Storm avoids requiring a separate schema by defining elements of a
64 schema in the object definition. By default, object attributes are
65 assigned a table column with the same name as the attribute. The
66 default behavior can be changed by setting the column option.
67
68 Circular references
69 package Person;
70 use Storm::Object;
71 storm_table( 'People' );
72
73 has 'id' => (
74 is => 'rw',
75 traits => [qw(PrimaryKey AutoIncrement)],
76 );
77
78 has 'spouse' => (
79 is => 'rw',
80 isa => 'Person',
81 weak_ref => 1,
82 );
83
84 • References to other Storm enabled classes are serialized
85 automatically using the primary key. This is accomplished by
86 setting the "isa" option to a Storm enabled class (type).
87
88 • In a scenario such as this, where two objects will reference each
89 other in a circular structure, it is necessary to set the weak_ref
90 option to avoid memory leaks. When constructing and using objects
91 with circular references, it is necessary to manage the "scope".
92 The scope stops objects from being garbage collected to early (i.e.
93 when the only references to them are weak.)
94
96 Relationships are devised in two ways. We demonstrated one manner in
97 the example above by setting an attributes "isa" option to a Storm
98 enabled class. This allows you to referance a singular object. Here we
99 will demonstrate making "one-to-many" and "many-to-many" relationships
100 using the "has_many" keyword.
101
102 One-to-many
103 package Person;
104 use Storm::Object;
105 storm_table( 'People' );
106
107 has 'id' => (
108 is => 'rw',
109 traits => [qw(PrimaryKey AutoIncrement)],
110 );
111
112 one_to_many 'pokemon' => (
113 foreign_class => 'Pokemon',
114 match_on => 'master',
115 handles => {
116 pokemon => 'iter',
117 }
118 );
119
120 package Pokemon;
121 use Storm::Object;
122 storm_->table( 'Pokemon' );
123
124 has 'id' => (
125 is => 'rw',
126 traits => [qw(PrimaryKey AutoIncrement)],
127 );
128
129 has 'master' => (
130 is => 'rw',
131 isa => 'Person',
132 weak_ref => 1,
133 );
134
135 Here, we define the components of a relationship between the Person
136 class and the Pokemon class uing the "one_to_many" keyword.
137
138 • The "foreign_key => master" denotes that the relationship is made
139 by matching the primary key of the Person with the c<master>
140 attribute of the Pokemon.
141
142 • Using the "handles" option, we create the "pokemon" method for
143 Person. This method returns a Person's Pokemon in the form of a
144 Storm::Query::Select::Iterator object.
145
146 • To add another Pokemon to a Person, create a new Pokemon and set
147 the "master" attribute to a $person.
148
149 Many-to-many
150 package Person;
151 use Storm::Object;
152 storm_table( 'People' );
153
154 has 'id' => (
155 is => 'rw',
156 traits => [qw(PrimaryKey AutoIncrement)],
157 );
158
159 many_to_many 'pets' => (
160 foreign_class => 'Pets',
161 junction_table => 'PeoplesPets',
162 local_match => 'person',
163 foreign_match => 'pet',
164 handles => {
165 parents => 'iter',
166 add_pet => 'add',
167 remove_pet => 'remove',
168 }
169 )
170
171 package Pet;
172 use Storm::Object;
173 storm_table( 'Pets' );
174
175 has 'id' => (
176 is => 'rw',
177 traits => [qw(PrimaryKey AutoIncrement)],
178 );
179
180 many_to_many 'care_takers' => (
181 foreign_class => 'Pets',
182 junction_table => 'PeoplesPets',
183 local_match => 'person',
184 foreign_match => 'pets',
185 handles => {
186 care_takers => 'iter',
187 add_care_taker => 'add',
188 remove_care_taker => 'remove',
189 }
190 )
191
192 • In a many-to-many relationship, a junction_table is required to
193 form the relationship. This is specified as an option to the
194 "many_to_many" keyword.
195
196 • We also need to define the columns in the junction_table that will
197 be used to identify the components of the relationship. This is
198 done with local_match and foreign_match options. local_match is the
199 column in the junction table to match with the primary key of
200 defining class, while foreign_match is the the column to match with
201 the primary key of the foregin class.
202
203 • Using the "handles" option, we create methods for retrieving a
204 Storm::Query::Select::Iterator, as well as methods for adding and
205 removing pets/caretakers. "$pet->add_care_taker( $person )" is
206 synanamous with "$person->add_pet( $pet )" and
207 "$pet->remove_care_taker( $person )" is synanamous with
208 "$person->remove_pet( $pet )".
209
210 • As of version 0.05, Storm will automatically fill in the
211 "junction_table", "local_match", and forerign match if you do not
212 supply them.
213
214 package Person;
215 use Storm::Object;
216 storm_table( 'People' );
217
218 has 'id' => (
219 is => 'rw',
220 traits => [qw(PrimaryKey AutoIncrement)],
221 );
222
223 many_to_many 'pets' => (
224 foreign_class => 'Pets',
225 handles => {
226 parents => 'iter',
227 add_pet => 'add',
228 remove_pet => 'remove',
229 }
230 )
231
232 package Pet;
233 use Storm::Object;
234 storm_table( 'Pets' );
235
236 has 'id' => (
237 is => 'rw',
238 traits => [qw(PrimaryKey AutoIncrement)],
239 );
240
241 many_to_many 'care_takers' => (
242 foreign_class => 'Pets',
243 handles => {
244 care_takers => 'iter',
245 add_care_taker => 'add',
246 remove_care_taker => 'remove',
247 }
248 )
249
251 Storm provides queries for the four basic data operations Create, Read,
252 Update, and Delete (CRUD) as well as a "select" query for searching.
253
254 Insert
255 $storm->insert( @objects );
256
257 Inserts @objects into the database. Objects may onle be inserted if
258 they do not already exist in the database. An error will be thrown if
259 you try to insert an object that already exists. An error will also be
260 thrown if the object has a primary key and it is undef (unless using
261 the AutoIncrement trait.)
262
263 Lookup
264 $storm->lookup( $class, @object_ids );
265
266 Retrieves object from the database by primary key. The $class attribute
267 is required so Storm knows where to find and how to inflate the
268 objects. If any of the object's attributes reference other Storm
269 enabled objects, they will be looked up and inflated as well. This will
270 continue until all dependent object have been retrieved and inflated.
271
272 Update
273 $storm->update( @objects );
274
275 Updates the state of the @objects in the database. If you try to call
276 "update" on an object that is not already in the database, an error
277 will be thrown. Only the @objects passed to "update" will be affected,
278 any Storm enabled objects they reference will not updated in the
279 database. You must call "update" on them yourself.
280
281 Delete
282 $storm->delete( @objects );
283
284 Deletes the @objects from the database. The local references to them
285 will still exists until you destroy them or they go out of scope.
286
288 Searching is possible using a select query. The select query is a
289 little more complex than it's counterparts.
290
291 Iterators
292 $query = $storm->select( 'Person' );
293 $iter = $query->results;
294
295 while ( $object = $iter->next ) {
296
297 ... do stuff with $object ...
298
299 }
300
301 Calling the "results" method on a select query returns a
302 Storm::Query::Select::Iterator for iterating over the result set.
303
304 Where
305 $query = $storm->select( 'Person' );
306 $query->where( '.last_name', '=', 'Simpson' );
307 $query->where( '.age', '>', 10 );
308 $iter = $query->results;
309
310 Use Storm::Query::Select's "where" method to select specific objects.
311
312 • The following comparisons are supported: =, <>, <, <=, =>, IN, NOT
313 IN, BETWEEN, LIKE, NOT LIKE
314
315 • It is possible to use attributes in a comparison with the
316 ".attribute" notation (to distinguish them from regular strings.)
317
318 $query->where( '.spouse.first_name', '=', 'Marge' );
319
320 If the attribute is also a Storm enabled object you can can
321 reference it's attributes in the comparison as well.
322
323 Placeholders
324 $query->where( '.age', '>', '?' );
325 $iter = $query->results( 10 );
326
327 You can use a "?" as placeholder. Supply the arguments to replace the
328 placeholders when calling the "results" method.
329
330 Order-by
331 $query->order_by( '.lastname', '.age DESC' );
332
333 Use the "order_by" method to sort the results.
334
336 The scope ensures that objects aren't garbage collected to early. As
337 objects are inflated from the database, the are pushed onto the live
338 object scope, increasing their reference count.
339
340 Let's define out person class to use as an example.
341
342 package Person;
343 use Storm::Object;
344
345 has 'id' => (
346 is => 'rw',
347 traits => [qw(PrimaryKey AutoIncrement)],
348 );
349
350 has 'name' => (
351 is => 'rw',
352 );
353
354 has 'spouse' => (
355 is => 'rw',
356 isa => 'Person',
357 weak_ref => 1,
358 );
359
360 Now, insert some objects into the database.
361
362 $storm->insert(
363 Person->new( name = 'Homer' ),
364 Person->new( name = 'Marge' )
365 );
366
367 And then we can link them together:
368
369 {
370 my $scope = $storm->new_scope;
371
372 my ( $homer, $marge ) = $storm->lookup( $homer_id, $marge_id );
373 $homer->spouse( $marge );
374 $marge->spouse( $homer );
375 $storm->update( $homer, $marge );
376 }
377
378 Now we can we can load the objects from the database like this:
379
380 {
381 my $scope = $storm->new_scope;
382
383 my $homer = $storm->lookup( $homer_id );
384
385 print $homer->spouse->name; # Marge
386 }
387
388 {
389 my $scope = $storm->new_scope;
390
391 my $marge = $storm->lookup( $marge_id );
392
393 print $marge->spouse->name; # Homer Simpson
394
395 refaddr( $marge ) == refaddr( $marge->spouse->spouse ); # true
396 }
397
398 When the initial object is loaded, all the objects that the initial
399 object depends on will be loaded. This will continue until all
400 dependent objects have been inflated from the database.
401
402 If we did not use a scope, by the time $homer his spouse attribute
403 would have been cleared because there is no other reference to Marge.
404 Here is a code snippet that demonstrates why:
405
406 sub get_homer {
407 my $homer = Person->new( name => 'Homer' );
408 my $marge = Person->new( name => 'Marge' );
409
410 $homer->spouse( $marge );
411 $marge->spouse( $homer );
412
413 return $homer;
414
415 # at this point $homer and $marge go out of scope
416 # $homer has a refcount of 1 because it's the return value
417 # $marge has a refcount of 0, and gets destroyed
418 # the weak reference in $homer->spouse is cleared
419 }
420
421 my $homer = get_homer();
422
423 $homer->spouse; # this returns undef
424
425 By using this idiom:
426
427 {
428 my $scope = Storm->new_scope;
429
430 ... do all Storm work in here ...
431 }
432
433 You are ensuring that the objects live at least as long as necessary.
434
435 In a web application context, you usually create one new scope per
436 request.
437
438 Credit
439 The live object scope was largely inspired by the KiokuDB module. Some
440 of the code and documentation for this functionality was taken directly
441 from the KiokuDB source (and possibly modified.)
442
444 When using a supporting databse, you can use the "do_transaction"
445 method to execute a code block and commit the transaction.
446
447 eval {
448 $storm->do_transaction( sub {
449
450 ... do work on $storm ...
451
452 });
453 }
454
455 print $@ if $@; # prints error
456
457 The transaction will only be committed if they block executes
458 successfully. If any exceptions are thrown, the transaction will be
459 rolled back. It is recommended that you execute the transaction inside
460 an eval block to trap any errors that are thrown. Alternatively, you
461 can use a module like TryCatch or Try::Tiny to trap errors.
462
464 The policy is used to determine what data type is used by the DBMS to
465 store a value. The policy also determines how different types of values
466 are inflated/deflated.
467
468 package My::Policy;
469 use Storm::Policy;
470
471 define 'DateTime', 'DATETIME';
472
473 transform 'DateTime',
474 inflate { DateTime::Form::SQLite->parse_datetime( $_ ) },
475 deflate { DateTime::Form::SQLite->format_datetime( $_ ) };
476
477
478 package main;
479 use Storm;
480
481 $storm->new( source => ..., policy => 'My::Policy' );
482
483 "define"
484 Use the "define" keyword to determine what data type the DBMS
485 should used to store a value of the given type. In this case we
486 want DateTime objects to be stored in the database using the
487 "DATETIME" data type.
488
489 "transform"
490 Use the "transform" keyword for setting a custom inflator/deflator
491 for a type.
492
493 The inflator is defined using the "inflate" keyword. The $_ special
494 variable will be set to the value to be inflated. The inflator is
495 expected to return the inflated value.
496
497 The deflator is defined using the "deflate" keyword. The $_ special
498 variable will be set to the value to be deflated. The deflator is
499 expected to return the deflated value.
500
501 Credit
502 The policy was inspired by the Fey::ORM module. Some of the code this
503 functionality was taken directly from theFey::ORM source (and possibly
504 modified.)
505
507 Aeolus is the greek god of the wind. Aeolus helps manage your database
508 installation. With Aeolus you can easily install and remove the tables
509 your classes need to store their data.
510
511 $storm->aeolus->install_class( 'Person' );
512
513 See Storm::Aeolus for more information.
514
516 Jeffrey Ray Hallock <jeffrey.hallock at gmail dot com>
517
518 Dave Rolsky <autarch@urth.org>
519
520 Yuval Kogman <nothingmuch@woobling.org>
521
523 Copyright (c) 2010-2011 Jeffrey Ray Hallock.
524
525 Copyright (c) 2010-2011 Dave Rolsky.
526
527 Copyright (c) 2008, 2009 Yuval Kogman, Infinity Interactive.
528
529 All rights reserved. This program is free software; you can redistribute it
530 and/or modify it under the same terms as Perl itself.
531
533 Hey! The above document had some coding errors, which are explained
534 below:
535
536 Around line 252:
537 '=item' outside of any '=over'
538
539
540
541perl v5.34.0 2021-07-22 Storm::Tutorial(3)