Module: relationships

This module provides support for defining relationships between your Elixir entities. Elixir currently supports two syntaxes to do so: the default Attribute-based syntax which supports the following types of relationships: ManyToOne, OneToMany, OneToOne and ManyToMany, as well as a DSL-based syntax which provides the following statements: belongs_to, has_many, has_one and has_and_belongs_to_many.

Attribute-based syntax

The first argument to all these "normal" relationship classes is the name of the class (entity) you are relating to.

Following that first mandatory argument, any number of additional keyword arguments can be specified for advanced behavior. See each relationship type for a list of their specific keyword arguments. At this point, we'll just note that all the arguments that are not specifically processed by Elixir, as mentioned in the documentation below are passed on to the SQLAlchemy relation function. So, please refer to the SQLAlchemy relation function's documentation for further detail about which keyword arguments are supported.

You should keep in mind that the following keyword arguments are automatically generated by Elixir and should not be used unless you want to override the value provided by Elixir: uselist, remote_side, secondary, primaryjoin and secondaryjoin.

Additionally, if you want a bidirectionnal relationship, you should define the inverse relationship on the other entity explicitly (as opposed to how SQLAlchemy's backrefs are defined). In non-ambiguous situations, Elixir will match relationships together automatically. If there are several relationships of the same type between two entities, Elixir is not able to determine which relationship is the inverse of which, so you have to disambiguate the situation by giving the name of the inverse relationship in the inverse keyword argument.

Here is a detailed explanation of each relation type:

ManyToOne

Describes the child's side of a parent-child relationship. For example, a Pet object may belong to its owner, who is a Person. This could be expressed like so:

class Pet(Entity):
    owner = ManyToOne('Person')

Behind the scene, assuming the primary key of the Person entity is an integer column named id, the ManyToOne relationship will automatically add an integer column named owner_id to the entity, with a foreign key referencing the id column of the Person entity.

In addition to the keyword arguments inherited from SQLAlchemy's relation function, ManyToOne relationships accept the following optional arguments which will be directed to the created column:

Option Name Description
colname Specify a custom column name.
required Specify whether or not this field can be set to None (left without a value). Defaults to False, unless the field is a primary key.
primary_key Specify whether or not the column(s) created by this relationship should act as a primary_key. Defaults to False.
column_kwargs A dictionary holding any other keyword argument you might want to pass to the Column.

The following optional arguments are also supported to customize the ForeignKeyConstraint that is created:

Option Name Description
use_alter If True, SQLAlchemy will add the constraint in a second SQL statement (as opposed to within the create table statement). This permits to define tables with a circular foreign key dependency between them.
ondelete Value for the foreign key constraint ondelete clause. May be one of: cascade, restrict, set null, or set default.
onupdate Value for the foreign key constraint onupdate clause. May be one of: cascade, restrict, set null, or set default.
constraint_kwargs A dictionary holding any other keyword argument you might want to pass to the Constraint.

Additionally, Elixir supports the belongs_to statement as an alternative, DSL-based, syntax to define ManyToOne relationships.

OneToMany

Describes the parent's side of a parent-child relationship when there can be several children. For example, a Person object has many children, each of them being a Person. This could be expressed like so:

class Person(Entity):
    parent = ManyToOne('Person')
    children = OneToMany('Person')

Note that a OneToMany relationship cannot exist without a corresponding ManyToOne relationship in the other way. This is because the OneToMany relationship needs the foreign key created by the ManyToOne relationship.

In addition to keyword arguments inherited from SQLAlchemy, OneToMany relationships accept the following optional (keyword) arguments:

Option Name Description
order_by Specify which field(s) should be used to sort the results given by accessing the relation field. You can either use a string or a list of strings, each corresponding to the name of a field in the target entity. These field names can optionally be prefixed by a minus (for descending order).

Additionally, Elixir supports an alternate, DSL-based, syntax to define OneToMany relationships, with the has_many statement.

Also, as for standard SQLAlchemy relations, the order_by keyword argument

OneToOne

Describes the parent's side of a parent-child relationship when there is only one child. For example, a Car object has one gear stick, which is represented as a GearStick object. This could be expressed like so:

class Car(Entity):
    gear_stick = OneToOne('GearStick', inverse='car')

class GearStick(Entity):
    car = ManyToOne('Car')

Note that a OneToOne relationship cannot exist without a corresponding ManyToOne relationship in the other way. This is because the OneToOne relationship needs the foreign_key created by the ManyToOne relationship.

Additionally, Elixir supports an alternate, DSL-based, syntax to define OneToOne relationships, with the has_one statement.

ManyToMany

Describes a relationship in which one kind of entity can be related to several objects of the other kind but the objects of that other kind can be related to several objects of the first kind. For example, an Article can have several tags, but the same Tag can be used on several articles.

class Article(Entity):
    tags = ManyToMany('Tag')

class Tag(Entity):
    articles = ManyToMany('Article')

Behind the scene, the ManyToMany relationship will automatically create an intermediate table to host its data.

Note that you don't necessarily need to define the inverse relationship. In our example, even though we want tags to be usable on several articles, we might not be interested in which articles correspond to a particular tag. In that case, we could have omitted the Tag side of the relationship.

If the entity containing your ManyToMany relationship is autoloaded, you must specify at least one of either the remote_side or local_side argument.

In addition to keyword arguments inherited from SQLAlchemy, ManyToMany relationships accept the following optional (keyword) arguments:

Option Name Description
tablename Specify a custom name for the intermediary table. This can be used both when the tables needs to be created and when the table is autoloaded/reflected from the database.
remote_side A column name or list of column names specifying which column(s) in the intermediary table are used for the "remote" part of a self-referential relationship. This argument has an effect only when your entities are autoloaded.
local_side A column name or list of column names specifying which column(s) in the intermediary table are used for the "local" part of a self-referential relationship. This argument has an effect only when your entities are autoloaded.
order_by Specify which field(s) should be used to sort the results given by accessing the relation field. You can either use a string or a list of strings, each corresponding to the name of a field in the target entity. These field names can optionally be prefixed by a minus (for descending order).
column_format Specify an alternate format string for naming the columns in the mapping table. The default value is defined in elixir.options.M2MCOL_NAMEFORMAT. You will be passed tablename, key, and entity as arguments to the format string.

DSL-based syntax

The following DSL statements provide an alternative way to define relationships between your entities. The first argument to all those statements is the name of the relationship, the second is the 'kind' of object you are relating to (it is usually given using the of_kind keyword).

belongs_to

The belongs_to statement is the DSL syntax equivalent to the ManyToOne relationship. As such, it supports all the same arguments as ManyToOne relationships.

class Pet(Entity):
    belongs_to('feeder', of_kind='Person')
    belongs_to('owner', of_kind='Person', colname="owner_id")

has_many

The has_many statement is the DSL syntax equivalent to the OneToMany relationship. As such, it supports all the same arguments as OneToMany relationships.

class Person(Entity):
    belongs_to('parent', of_kind='Person')
    has_many('children', of_kind='Person')

There is also an alternate form of the has_many relationship that takes only two keyword arguments: through and via in order to encourage a richer form of many-to-many relationship that is an alternative to the has_and_belongs_to_many statement. Here is an example:

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')

In the above example, a Person has many projects through the Assignment relationship object, via a project attribute.

has_one

The has_one statement is the DSL syntax equivalent to the OneToOne relationship. As such, it supports all the same arguments as OneToOne relationships.

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

The has_and_belongs_to_many statement is the DSL syntax equivalent to the ManyToMany relationship. As such, it supports all the same arguments as ManyToMany relationships.

class Article(Entity):
    has_and_belongs_to_many('tags', of_kind='Tag')

class Tag(Entity):
    has_and_belongs_to_many('articles', of_kind='Article')