Time for action adapting MetaRelation

In the Display class, we used the information about the type of relation stored by the MetaRelation metaclass. This is necessary because in recognizing that there is more than one type of relation, we need some way to indicate that when we define a new relation and act upon that information when creating a new class. Look at the following example code:

class A(Entity):
	pass
class B(Entity):
	pass
class AhasmanyB(Relation):
	a=A
	b=B

Here we express the relation between A and B to be one-to-many. If we would like to express the notion that an instance of A may refer only to a single B instance, we need some way to indicate that in the definition. One way of doing so is by reversing the assignments in the class definition of the variable:

class AreferstoasingleB(Relation):
	a=B
	b=A

The MetaRelation metaclass we defined earlier could act on such a definition as we arranged for the class dictionary of the relation being defined to be an OrderedDict, so in principle, we can act on the order of the definitions.

A slightly more explicit way of defining this is often clearer, so instead we opt for a relation_type attribute that can be assigned a string with the type of the relation. For example:

class AhasmanyB(Relation):
	a=A
	b=B
	relation_type='1:N'
class AreferstoasingleB(Relation):
	a=A
	b=B
	relation_type='N:1'

If we leave out the relation_type, a one-to-many relation is assumed.

What just happened?

Let's have a look at the changes and additions to MetaRelation needed to implement those semantics. We need two changes. The first is in the definition of the bridge table we use to administer the relation. We need an additional unique constraint here to enforce that in a one-to-many relation, the IDs in the column referring to the many side of the equation are unique.

This may sound counterintuitive, but let's say we have the following cars: a Volvo, a Renault, a Ford, and a Nissan. There are also two owners, John and Jill. Jill owns the Volvo and the Renault, and John the other cars. The tables might look like this:

Car

 

ID

make

1

Volvo

2

Renault

3

Ford

4

Nissan

Owner

 

ID

name

1

Jill

2

John

The table that reflects the ownership of the cars might look like this:

Ownership

 

Car

owner

1

1

2

1

3

2

4

2

We see that while a single owner may have many cars, it is the numbers in the Car column that are unique because of this relation.

In order to define a table with those additional uniqueness constraints and to make the information about the type of relation available in the classes that form both halves of a relation, we have to adapt the final part of the __new__() method in the MetaRelation metaclass:

Chapter9/entity.py

if relationdefinition or '_meta' in classdict:
		a = classdict['a']
		b = classdict['b']
		r = '1:N'0
		if 'relation_type' in classdict: r = classdict['relation_type']
		if not r in ('N:1','1:N'): raise KeyError("unknown relation_
type %s"%r)
		classdict['relation_type'] = r
		if not issubclass(a,AbstractEntity) : raise TypeError('a not 
an AbstractEntity')
		if not issubclass(a,AbstractEntity) : raise TypeError('b not 
an AbstractEntity')
		runique = ' ,unique(%s_id)'%a.__name__
		if r == '1:N' : runique = ' ,unique(%s_id)'%b.__name__
		sql = 'create table if not exists %(rel)s ( %(a)s_id references %(a)s on delete cascade, %(b)s_id references %(b)s on delete cascade, unique(%(a)s_id,%(b)s_id)%(ru)s)'%{'rel':classname,'a':a.__name__,'b':b.__name__,'ru':runique}
conn = sqlite.connect(classdict['_database'])
		conn.execute(sql)
		setattr(a,'get'+b.__name__,lambda self:getclass(self,b, 
classname))
		setattr(a,'get',get)
		setattr(b,'get'+a.__name__,lambda self:getclass(self,a, 
classname))
		setattr(b,'get',get)
		setattr(a,'add'+b.__name__,lambda self,entity:addclass(self, entity,b,
classname))
		setattr(a,'add',add)
		setattr(b,'add'+a.__name__,lambda self,entity:addclass(self, entity,a,
classname))
		setattr(b,'add',add)
		reltypes = getattr(a,'reltype',{})
		reltypes[b.__name__]=r
		setattr(a,'reltype',reltypes)
		reltypes = getattr(b,'reltype',{})
		reltypes[a.__name__]={'1:N':'N:1','N:N':'N:N','N:1':'1:N'}[r]
		setattr(b,'reltype',reltypes)
		relclasses = getattr(a,'relclass',{})
		relclasses[b.__name__]=b
		setattr(a,'relclass',relclasses)
		relclasses = getattr(b,'relclass',{})
		relclasses[a.__name__]=a
		setattr(b,'relclass',relclasses)
		joins = getattr(a,'joins',{})
		joins[b.__name__]=classname
		setattr(a,'joins',joins)
		joins = getattr(b,'joins',{})
		joins[a.__name__]=classname
		setattr(b,'joins',joins)
	return type.__new__(metaclass,classname,baseclasses,classdict)

The highlighted lines are the ones we added. The first set makes sure there is a relation_type attribute defined and if not, creates one with a default'1:N' value.

The second set of highlighted lines determines which column in the bridge table should receive an additional unique constraint and constructs the SQL query to create the table.

The final block of highlighted lines adds class attributes to both classes in the relation. All those attributes are dictionaries indexed by the name of an entity. The reltype attribute holds the type of the relation, so in a Car entity, we might obtain the type of relation with an Owner like this:

Car.reltype('Owner')

Which, if defined as in our previous example, will yield'N:1' (one or more cars may have a single owner).

Likewise, we can get information about the same relation from the perspective of the owner:

Owner.reltype('Car')

Which will yield the inverse,'1:N' (an owner may have more than one car).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.66.185