Relationship Examples

Documentation Index

  1. Has One
  2. Has Many
  3. Belongs To
  4. Has and Belongs To Many
  5. Polymorphic Relationships

Twistar is an Object Relational Mapper (ORM). It therefore defines ways of interacting with objects and other objects that have relationships with them. The code here is not necessarily great Twisted code; where typically you would expect to see DeferredLists and inlineCallbacks there are none to provide clarity to those new to Twisted.

More information on relationships can be found in the Relationship class.

Has One

Perhaps the simplest relationship is the Has One relationship. In this example, we will be using a User object that Has One Avatar object.

1 2 3 4 5 6 7 8 9 10

from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASONE = ['avatar'] class Avatar(DBObject): pass Registry.register(User, Avatar)

In this example, we specify that each user can have one avatar. The database at this point should have two tables:

  1. A users table with at least a column id.
  2. A avatars table with at least two columns: id and user_id.
It is also necessary to register our classes with the registry before we can start utilizing relationships (this is so the module can find and instantiate them when necessary).

For the HASONE class variable we can optionally give a dictionary that provides additional information:

1 2 3 4 5 6 7 8 9 10

from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASONE = [{'name': 'avatar', 'class_name': 'Avatar', foreign_key: 'user_id'}] class Avatar(DBObject): pass Registry.register(User, Avatar)

There are additional options as well: see the Relationships class for more information.

At this point we can use the relationship and assign user's avatar:

1 2 3 4 5 6 7

def onPicSave(picture, user): user.picture.set(picture) def onUserSave(user): Avatar(file="somewhere").save().addCallback(onPicSave, user) User(first_name="Bob").save().addCallback(onUserSave)

We can then get it:

1 2 3 4 5 6 7

def foundPicture(picture): print picture.file def foundUser(user): user.picture.get().addCallback(foundPicture) User.find(where=['first_name = ?', "Bob"], limit=1).addCallback(foundUser)

Has Many

Another relationship is used when one object "has many" of another. For instance, a User may have many Pictures.

1 2 3 4 5 6 7 8 9 10

from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASMANY = ['pictures'] class Picture(DBObject): pass Registry.register(User, Picture)

Again, the list assigned to HASMANY can have dictionaries in it just like HASONE that contain additional configuration options. For this relationship, the database should at this point should have two tables:

  1. A users table with at least a column id.
  2. A pictures table with at least two columns: id and user_id.
As in the previous example, we can get and set a user's pictures using a special property of user: pictures.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

from twisted.internet.defer import inlineCallbacks @inlineCallbacks def addPictures(): # set some pics user = yield User(first_name="Bob").save() picone = yield Picture(file="somewhere").save() pictwo = yield Picture(file="elsewhere").save() pictures = [picone, pictwo] yield user.pictures.set(pictures) # now get them pictures = yield user.pictures.get() print pictures[0].file print pictures[1].file addPictures()

Additionally, there is a clear() method that will clear all objects in a given has many relationship.

Belongs To

A good example of the "belongs to" property is the reverse of the "has many":

1 2 3 4 5 6 7 8 9 10

from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASMANY = ['pictures'] class Picture(DBObject): BELONGSTO = ['user'] Registry.register(User, Picture)

Assuming the DB structure is the same as in the "has many" example, you can now get and set the user that a particular picture belongs to (using get() and set() methods on picture.user). Additionally, there is a clear() method that will clear all objects in a given "belongs to" relationship.

Has and Belongs To Many

In a "has and belongs to many" relationship two objects have a many to many relationship. For instance, users and favorite colors. A user can have many favorite colors and a favorite color could belong to many users. In this example, there should be three tables — a users table, a favorite_colors table, and a favorite_colors_users table. The last on the list is a special table that stores the relationships between the colors and users. It has no primary key, and two columns: one for user_ids and one for favorite_color_ids. The table's name by convention should be the combination of the other two table names, joined with an underscore, and arranged alphabetically. The classes would look like:

1 2 3 4 5 6 7 8 9 10

from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HABTM = ['favorite_colors'] class FavoriteColor(DBObject): HABTM = ['users'] Registry.register(User, FavoriteColor)

You can now get and set the users of favorite colors and the favorite colors of users (using get() and set() methods). Additionally, there is a clear() method that will clear all objects in a given relationship.

Polymorphic Relationships

With a polymorhpic relationship, a model can belong to more than one other model using a single relationship. For instance, take the example where two models (say, Boy and Girl) both have many nicknames. Each Nickname can belong to either a Boy or a Girl. In this example, the tables for boys and girls may have whatever attributes you would like; the nicknames table, though, needs two columns in addition to an id and a value column. These columns are used to identify the id and type of the other class. In this case, they will be called nicknameable_id and nicknameable_type.

The classes look like this:

1 2 3 4 5 6 7 8 9 10 11 12 13

from twistar.dbobject import DBObject from twistar.registry import Registry class Boy(DBObject): HASMANY = [{'name': 'nicknames', 'as': 'nicknameable'}] class Girl(DBObject): HASMANY = [{'name': 'nicknames', 'as': 'nicknameable'}] class Nickname(DBObject): BELONGSTO = [{'name': 'nicknameable', 'polymorphic': True}] Registry.register(Boy, Girl, Nickname)

The use is pretty simple, and follows the same form as you would expect with a traditional "belongs to" relationship.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

def sayHi(nicknameable): print "Hello, my name is %s and I am a %s" % (nicknameable.name, nicknameable.__class__.__name__) def getPerson(nickname): nickname.nicknameable.get().addCallback(sayHi) def setNickname(nickname, boyOrGirl): boyOrGirl.nicknames.set([nickname]).addCallback(lambda _: getPerson(nickname)) def boySaved(boy): Nickname(value="Bob").save().addCallback(setNickname, boy) def girlSaved(girl): Nickname(value="Susie").save().addCallback(setNickname, girl) Boy(name="Robert").save().addCallback(boySaved) Girl(name="Susan").save().addCallback(girlSaved)

You can see that the nicknames for the boy are set via boy.nicknames.set; they are fetched as you would expect via boy.nicknames.get (with the same form for girls). For each nickname, by calling nickname.nicknameable.get you could end up with either a Boy or a Girl.

Documentation Index