Caching behavior

What's going on behind the scenes is that each store has an object cache. When an object is linked to a store, it will be cached by the store for as long as there's a reference to the object somewhere, or while the object is dirty (has unflushed changes).

Storm ensures that at least a certain number of recently used objects will stay in memory inside the transaction, so that frequently used objects are not retrieved from the database too often.

Flushing

When we tried to find Joe in the database for the first time, we've noticed that the id property was magically assigned. This happened because the object was flushed implicitly so that the operation would affect any pending changes as well.

This operation will touch every matching object in the database, and also objects that are alive in memory.

Committing

Everything we've done so far is inside a transaction. At this point, we can either make these changes and any pending uncommitted changes persistent by committing them, or we can undo everything by rolling them back.

Constructors

So, we've been working for too long with people only. Let's introduce a new kind of data in our model: companies. For the company, we'll use a constructor, just for the fun of it. It will be the simplest company class you've ever seen:

The id is still undefined because we haven't flushed it. In fact, we haven't even added the company to the store. We'll do that soon. Watch out.

References and subclassing

Now we want to assign some employees to our company. Rather than redoing the Person definition, we'll keep it as it is, since it's general, and will create a new subclass of it for employees, which include one extra field: the company id.

Pay attention to that definiton for a moment. Notice that it doesn't define what's already in person, and introduces the company_id, and a company property, which is a reference to another class. It also has a constructor, but which leaves the company alone.

As usual, we need a table. SQLite has no idea of what a foreign key is, so we'll not bother to define it.

They're both flushed to the database. Now, notice that the Circus company wasn't added to the store explicitly in any moment. Storm will do that automatically for referenced objects, for both objects (the referenced and the referencing one).

Let's create another company to check something. This time we'll flush the store just after adding it.

Now, let's use it to declare the many-to-many relationship in the company. Once more, we'll just stick the new attribute in the existent object. It may easily be defined at class definition time. Later we'll see another way to do that as well.

That's it! Really! Notice that we didn't even have to add them to the store, since it happens implicitly by linking to the other object which is already in the store, and that we didn't have to declare the relationship object, since that's known to the reference set.

We can now check them.

>>> sweets.accountants.count()
2
>>> circus.accountants.count()
1

Even though we didn't use the CompanyAccountant object explicitly, we can check it if we're really curious.

Multiple types with one query

Sometimes, it may be interesting to retrieve more than one object involved in a given query. Imagine, for instance, that besides knowing which companies have an employee named Ben, we also want to know who is the employee. This may be achieved with a query like follows:

The Storm base class

So far we've been defining our references and reference sets using classes and their properties. This has some advantages, like being easier to debug, but also has some disadvantages, such as requiring classes to be present in the local scope, what potentially leads to circular import issues.

To prevent that kind of situation, Storm supports defining these references using the stringified version of the class and property names. The only inconvenience of doing so is that all involved classes must inherit from the Storm base class.

Let's define some new classes to show that. To expose the point, we'll refer to a class before it's actually defined.

Note that in the first find, nothing was called, since the object was still in memory and cached. Then, we invalidated the object from Storm's internal cache and ensured that it was out-of-memory by triggering a garbage collection. After that, the object had to be retrieved from the database again, and thus the hook was called (and not the constructor!).

Executing expressions

Storm also offers a way to execute expressions in a database-agnostic way, when that's necessary.

This mechanism is used internally by Storm itself to implement the higher level features.

Auto-reloading values

Storm offers some special values that may be assigned to attributes under its control. One of these values is AutoReload. When used, it will make the object automatically reload the value from the database when touched. Even primary keys may benefit from its use, as shown below.

This may be set as the default value for any attribute, making the object be automatically flushed if necessary.

Expression values

Besides auto-reloading, it's also possible to assign what we call a "lazy expression" to an attribute. Such expressions are flushed to the database when the attribute is accessed, or when the object is flushed to the database (INSERT/UPDATE time).

Notice that this is just an example of what may be done. There's no need to write SQL statements this way, if you don't want to. You may also use class-based SQL expressions provided in Storm, or even not use lazy expressions at all.

Aliases

So now let's say that we want to find every pair of people that work for the same company. I have no idea about why one would want to do that, but that's a good case for us to exercise aliases.

First, we import ClassAlias into the local namespace (mental note: this should be in storm.locals as well), and create a reference to it.

(Quiz for the attent reader: why is greater than being used in the query above?)

Debugging

Sometimes you just need to see which statements Storm is executing. A debug tracer built on top of Storm's tracing system can be used to see what's going on under the hood. A tracer is an object that gets notified when interesting events occur, such as when Storm executes a statement. A function to enable and disable statement tracing is provided. Statements are logged to sys.stderr by default, but a custom stream may also be used.

Much more!

There's a lot more about Storm to be shown. This tutorial is just a way to get initiated on some of the concepts. While your questions are not answered somewhere else, feel free to ask them in the mailing list.