elixir的版本为0.7.1,依赖的sqlalchemy版本为0.7.8,转载请注明出处。
Elixir是基于python界有名的ORM库SQLAlchemy做的封装。而且是轻量级的封装,它提供了更简单的方式来创建Python类并直接映射到关系数据库表(即通常所说的Active Record设计模式),类似于Django中的ORM。
下面为relationships.py源码中对四种关系的说明。
这个模块为在Elixir的实体之间定义映射关系提供支持。Elixir目前提供两种语法来定义关系:
默认的是使用基于属性的语法,它提供有如下几种关系:
ManyToOne, OneToMany, OneToOne and ManyToMany
另外一种就是基于领域特定语言(Domain Specific Language)的语法,相应提供有如下几种关系:
belongs_to, has_many, has_one and has_and_belongs_to_many
======================
基于属性的语法
======================
这些负责建立关系映射的方法接受的第一个参数,是需要被关系到的实体类(entity class)的类名。
在这个强制参数之后,其他参数都用来描述一些特定高级行为,这可以通过查看每个关系类型的特定关键参数列表来了解。在这里需要指出的是,所有在下面文档里提到的那些不是专门由Elixir负责处理的参数都将被传递给SQLAlchemy的relation函数处理,请查看SQLAlchemy的SQLAlchemy的relation函数的文档,以了解更多这些参数的详细内容<http://www.sqlalchemy.org/docs/05/reference/orm/mapping.html#sqlalchemy.orm.relation>。
记住下面这些参数是Elixir自动生成的,在你没必要重定义Elixir提供的这些参数的值的时候,切记不要使用这些参数:
`uselist`,`remote_side`, `secondary`, `primaryjoin` and `secondaryjoin`
另外,你在定义一个双向关系的时候,你必须在另外那个实体明确定义一个反向关系(inverse),这正好与SQLAlchemy的backrefs的定义方式不同。在没有歧义的情况下,Elixir将自动匹配实体之间定义的关系。但如果同一类型多个的关系在两个实体分别都作定义,那么Elixir将无法确定那个关系是关系反向的一方。所以,你需要为另外那个实体中定义的关系的inverse参数提供被反向的关系名,以明确一个反向关系。
下面是每种关系的详解:
`ManyToOne`
-----------
在子实体的一边,描述一个有多个子实体的父子关系(parent-child relationship)。
比如,描述某个Pet类实体的所有者是一个Person类实体,那么可以表达为:
class Pet(Entity):
owner = ManyToOne('Person')
在这里,我们背后假设了Person实体的主键是一个叫`id`的整型列,那么定义的ManyToOne关系将会在Pet实体的中自动添加一个名为`owner_id`的整型列。
此外,除了从SQLAlchemy的relation函数关键字继承的参数,ManyToOne关系还支持下面这些可选参数,他们将指导新列的创建过程:
可选参数名 | 描述 |
``colname`` | 为外键列指定一个自定名称。 这个参数接受一个字符串或者一个字符串列表,字符串表的字符数必须与被关联到的实体的主键的列数相等。 如果没有使用这个参数,那么新建的外键列的名称的将采用在options.FKCOL_NAMEFORMAT中定义的默认方式,形式为:"%(relname)s_%(key)s",这里的'relname'是实体所定义的ManyToOne关系字段的名称,'key'是被关联到的目标实体的主键列的名称。因此,在上面的例子中,外键列的名称当然就是"owner_id"。 |
``required`` | 指定这个字段是否可以为空(即不给定该列的值),除非为所在实体的主键,一般默认值为`False`。 |
``primary_key`` | 指定该关系所创建的列是否作为所在实体的主键列。 |
``column_kwargs`` | 保存一个需要传递给列的其它参数名及其值的字典。 |
``target_column`` | 指定被关联到的目标实体中的作为该关系所在实体的外键的列。 默认使用被关联到的实体的主键列。 |
下面的可选参数用于自定义所创建的外键约束(ForeignKeyConstraint):
可选参数名 | 描述 |
``use_alter`` | 如果为True,sqlalchemy将会在第二个SQL语句中添加这个约束(这与创建表的语句不同),这允许在表间定义一个环状外键依赖关系。 |
``ondelete`` | 外键约束ondelete条款。 可以是以下之一: `cascade`, `restrict`, `set null`或者`set default`。 |
``onupdate`` | 外键约束onupdate条款。 可以是以下之一: `cascade`, `restrict`, `set null`或者`set default`。 |
``column_kwargs`` | 一个保存有其余需要传递给约束的参数名及其值的字典。 |
在某些时候,你可能想要明确地声明外键列,而不是自动生成,可能的原因如:你想要精确的声明外键列的相关参数,以及使用column_kwargs参数使你的代码变得有些丑,或者因为你的外键列名与你的属性名相冲突(这种情况会抛出一个异常)。这时,你可以使用`field`参数来指定一个已经声明的字段来作为外键。比如:
class Pet(Entity):
owner_id = Field(Integer, colname='owner')
owner = ManyToOne('Person', field=owner_id)
可选参数名 | 描述 |
``field`` | 指定一个预先声明的字段来作为外键列,这个参数与colname、column_kwargs不要同时使用。 |
此外,Elixir支持使用belongs_to语句来代替ManyToOne,这是一个基于DSL的语法形式而已。
`OneToMany`
-----------
在父实体的一边,描述一个有多个子实体的父子关系(parent-child relationship)。
比如,如果要描述一个Person类实体有多个children类实体,而每一个children类实体又都是一个Person类实体,可以表达如下:
class Person(Entity):
parent = ManyToOne('Person')
children = OneToMany('Person')
注意,`OneToMany`关系不能在没有定义与之对应的`ManyToOne`关系的情况下存在,因为`OneToMany`关系需要使用由`ManyToOne`关系创建的外键(注:他们不一定像上面这样总是同时定义在一个实体种,大多数时候还是在两个不同的实体中定义)。
此外,除了从SQLAlchemy的relation函数关键字继承的参数,`OneToMany`关系支持下面这些可选参数:
可选参数名 | 描述 |
``order_by`` | 指定对通过被关联到的实体,对用来定义ManyToOne关系的字段的访问结果的集合,将依据哪个字段进行排序。注意,这个排序只在从数据库加载实体对象集合的时侯被应用,后来追加入实体集合的对象,将不会在运行于内存中的集合里得到重新排序。 这个参数接受一个字符串或者一个字符串列表,字符串列表中的每个字符串都与结果集合中实体的字段相对应。这些字段名前都可以加上一个可选的负号前缀代表降序排列。 |
``filter`` | 为关系指定一个过滤准则(作为一个条款),这个准则可以用由Elixir为关系创建的`and_`方式连接两个普通过滤准则(类似于主键的连接)。例如:boston_addresses = OneToMany('Address', filter=Address.city == 'Boston') |
|
此外,Elixir支持使用has_many语句来代替OneToMany,这是一个基于DSL的语法形式。
`OneToOne`
----------
在父实体的一边,定义一个仅有一个子实体时父子关系(parent-child relationship)。
比如一个`Car`对象只有一个变速操纵杆(gear stick),用`GearStick`对象代表。那么这个关系可以表述如下:
class Car(Entity):
gear_stick = OneToOne('GearStick', inverse='car')
class GearStick(Entity):
car = ManyToOne('Car')
注意`OneToOne`关系不能在没有与之对应的`ManyToOne`关系的情况下存在,因为`OneToOne`关系需要使用由`ManyToOne`关系创建的外键。
此外,Elixir支持使用has_one语句来代替OneToOne,这是一个基于DSL的语法形式。
`ManyToMany`
------------
描述如下关系:一个类型的实体可以是关联到多个另一个类型的对象,而另一个类型的对象也可以关联到多个这个类型的对象。
比如,一个`Article`对象可以个多个标签(tags),而一个标签可以被使用到多个文章(articles)对象中。这可以表达如下:
class Article(Entity):
tags = ManyToMany('Tag')
class Tag(Entity):
articles = ManyToMany('Article')
在这个`ManyToMany`关系背后,将会自动创建一个中间表格来为这些数据提供支持。
注意,你在其实不必总是定义一个反向关系。比如在我们这个例子里,尽管我们想让标签在多个文章中使用,但我们也许不会对某个特定标签究竟在哪些文章中使用感兴趣。因此,我们可以省略关系`Tag`实体的一边的定义。
如果你的`ManyToMany`是一个自引用的实体,当然包含它的实体已被自动加载(并且你不想手动指定primaryjoin和secondaryjoin参数),那么你还必须指定`remote_colname`和`local_colname`的其中之一。
此外,除了从SQLAlchemy的relation函数关键字继承的参数,`OneToMany`关系支持下面这些可选参数:
可选参数名 | 描述 |
``tablename`` | 为中间表指定一个表名。无论是新创建,还是从数据库自动加载/反射的,均可以使用这个参数。 如果不使用这个参数,则Elixir将根据关系中两个实体的表格自动生成一个表名。如果存在这个参数,则根据这个参数生成表名。 尽管这是一个可选参数,但是在你不能准确的知道自动生成的表名的情况下,建议你最好能使用这个参数。 |
``schema`` | 为中间表格指定一个模式(schema)。 无论是新创建,还是从数据库自动加载/反射的,均可以使用这个参数。 |
``remote_colname`` | 一个引用自远程(或者说被关联到的)实体表的,用来指定中间表列名称的字符串或字符串列表。 |
``local_colname`` | 一个引用自远程(或者说被关联到的)实体表的,用来指定中间表列名称的字符串或字符串列表。 |
``table`` | 使用一个已创建的表。 如果使用这个参数,Elixir将使用一个给定的表,而不再为关系自动生成一个表。 |
``order_by`` | 指定对通过被关联到的实体,访问定义ManyToMany关系的字段的结果的集合,将依据哪个字段进行排序。注意,这个排序只在从数据库加载实体对象集合的时侯被应用,后来追加入实体集合的对象,将不会在运行于内存中的集合里得到重新排序。 这个参数接受一个字符串或者一个字符串列表,字符串表中的每个字符串都与结果集合中实体的字段相对应。这些字段名前都可以加上一个可选的负号前缀代表降序排列。 |
``ondelete`` | 外键约束ondelete条款。 可以是以下之一: `cascade`, `restrict`, `set null`或者`set default`。 |
``onupdate`` | 外键约束onupdate条款。 可以是以下之一: `cascade`, `restrict`, `set null`或者`set default`。 |
``column_kwargs`` | 一个保存有其余需要传递给约束的参数名及其值的字典。 |
``column_format`` | 将被弃除的参数。一个用来代替为映射表格里的列指定名称的格式字符串。默认值定义在`elixir.options.M2MCOL_NAMEFORMAT`中。 你可以为格式字符串传递`tablename`, `key`, `entity`等参数。 |
================
DSL-based 语法
================
DSL语句提供一个在实体间定义各种关系的替代方式。这些定义语句的第一个参数是关系的名称。第二个参数是被关联的对象的类型('kind'),一般使用`of_kind`作为关键字。
`belongs_to`
------------
belongs_to语句是ManyToOne关系的DSL语法等价形式。它支持的参数与ManyToOne相同。eg:
class Pet(Entity):
belongs_to('feeder', of_kind='Person')
belongs_to('owner', of_kind='Person', colname="owner_id")
`has_many`
----------
has_many语句是OneToMany关系的DSL语法等价形式。它支持的参数与OneToMany相同。eg:
class Person(Entity):
belongs_to('parent', of_kind='Person')
has_many('children', of_kind='Person')
这里还有一个仅需要多使用两个参数`through`和`via`的复杂形式,即可替代`has_and_belongs_to_many`语句,定义一个many-to-many关系。下面有一个例子:
class Person(Entity):
has_field('name', Unicode)
has_many('assignments', of_kind='Assignment')
has_many('projects', through='assignments', via='project')
class Assignment(Entity):
has_field('start_date', DateTime)
belongs_to('person', of_kind='Person')
belongs_to('project', of_kind='Project')
class Project(Entity):
has_field('title', Unicode)
has_many('assignments', of_kind='Assignment')
在上面的例子中,一个Person实体通过(through)中间关系对象Assignment的project属性(via='project')拥有了多个Project,即实现了对projects关系的定义。
`has_one`
---------
has_one语句是OneToOne关系的DSL语法等价形式。它支持的参数与OneToOne相同。
class Car(Entity):
has_one('gear_stick', of_kind='GearStick', inverse='car')
class GearStick(Entity):
belongs_to('car', of_kind='Car')
`has_and_belongs_to_many`
-------------------------
has_and_belongs_to_many语句是ManyToMany关系的DSL语法等价形式。它支持的参数与ManyToMany相同。eg:
class Article(Entity):
has_and_belongs_to_many('tags', of_kind='Tag')
class Tag(Entity):
has_and_belongs_to_many('articles', of_kind='Article')