>> Relationships :




Introduction

bulb provides a group of classes used to structure and manipulate relationships and properties.
Only the most important things have been introduced, in order to leave a lot of flexibility to the package's user and not to burden him with useless things.
Furthermore, when you work with a database, the personalization of each request is the key of fast and powerful interactions. So don't hesitate to make your own request, for complex works.



Relationships models

Note that all relationships models in each application, must be written in files named node_models.py.


The Relationship instances possess some parameters which define their behaviour in the database :

  • rel_type (required) : This parameter defines the relationship type. It must be a string.
    See : https://neo4j.com/docs/getting-started/current/graphdb-concepts/#graphdb-relationship-types

  • direction (optional, default="from") : Must be "from", "to", or "bi". If it is "from", the relationship will be an arrow that starts from the self node_model's instance to other node_models' instances. If it is "to" it will be the opposite case : the relationship will be an arrow that starts from other node_models' instances to the self node_model's instance. Finally if it is "bi", it will be two relationships that will work by peers, one from and one to the self node_model's instance : a bi-directional relationships will be created.

  • properties_fields (optional, default=None) : A dict of properties for "all in node_model" syntax. If the Relationship classes are out of the node_model classes, this argument will be None.
  • start (optional, default=None) : This parameter must be a node_model class, its name or "self". It applies a start constraint to the relationship.

  • target (optional, default=None) : This parameter must be a node_model class, its name or "self". It applies a target constraint to the relationship.

  • auto (optional, default=False) : This parameter must be a boolean. If it is True, the relationship is allowed to be applied on a single node, which will be the start and the target of the relationship.

  • on_delete (optional, default="PROTECT") : This parameter must be "PROTECT" or "CASCADE". It defines the behavior of the related nodes. If it is "PROTECT", if the self node object of the relationship is deleted, nothing happen for the nodes that are related to it (A simple DETACH DELETE command is run). On the other hand, if it is "CASCADE", the other nodes will be deleted in the same time as the self node object.

  • unique (optional, default=False) : This parameter must be a boolean. If it is True the relationship will be unique.


  • Implement relationships models

There are two different syntaxes to implement relationships into your project, but both are using the Relationship class imported from bulb.db.node_models.

Let's see the two different ways of using relationships :

  • The "all in node_models" syntax

With this syntax, you can define the Relationship as an attribute of a node_model. Useful and convenient if you don't have to rse the relationship configuration again for another node_model.

node_models.py

from bulb.db import node_models
import datetime


class Article(node_models.Node):
    title = node_models.Property(required=True,
                                 unique=True)
    content = node_models.Property(required=True)
    publication_datetime = node_models.Property(default=datetime.datetime.now)
    authors = node_models.Relationship(rel_type="WROTE",
                                       direction="to",
                                       start="User",
                                       target="self",
                                       on_delete="CASCADE")


  • The "distinct" syntax

With this syntax, you can define a Relationship model and reuse it for multiple cases.

node_models.py

from bulb.db import node_models
import datetime

class RelatedAuthorsRelationship(node_models.Relationship):
    rel_type = "WROTE"
    direction = "to"
    start = "User"
    target = "self"
    on_delete = "CASCADE"


class Article(node_models.Node):
    title = node_models.Property(required=True,
                                 unique=True)
    content = node_models.Property(required=True)
    publication_datetime = node_models.Property(default=datetime.datetime.now)
    authors = RelatedAuthorsRelationship()


class Comment(node_models.Node):
    content = node_models.Property(required=True)
    author = RelatedAuthorsRelationship(unique=True)



  • Access to the relationships instances

When the previous step is completed, you can access to the Relationship instances through the related node attribute.

See this example (based on the previous examples) :

node_models.py

(...)

>>> my_article = Article.get(uuid="872a5f767485486a853e5d2886850fa2")
>>> my_article.authors
<RelatedAuthorsRelationship object(uuid="f37a4ae83e994aa8812b42de52c11b70")>

These instances will able you to work with the relationships.



  • Create relationships

Create the relationship is the easiest step because each configurations and behaviours have been defined in the previous step. To do this you only have to use the add() method of instances of the Relationship class.

This method can take 3 parameters :

  • instance (required if not uuid) : A node_model's instance, to which the relationship will target.

  • uuid (required if not instance) : A node_model's uuid, to which the relationship will target.

  • properties (optional) : The properties dictionary to fill if the relationship take one or more properties.

Keep in mind the previous example where we've defined the RelatedAuthorsRelationship when you read this demonstration :

node_models.py

from bulb.db import node_models
from bulb.contrib.auth.node_models import User
import datetime

class RelatedAuthorsRelationship(node_models.Relationship):
    rel_type = "WROTE"
    direction = "to"
    start = "User"
    target = "self"
    on_delete = "CASCADE"


class Article(node_models.Node):
    title = node_models.Property(required=True,
                                 unique=True)
    content = node_models.Property(required=True)
    publication_datetime = node_models.Property(default=datetime.datetime.now)
    authors = RelatedAuthorsRelationship()

first_article = Article.create(title="A great article !",
                               content="Lorem ipsum...")

john = User.get(email="john@mail.com")

# Add john as author of the first article
first_article.authors.add(john)



  • Work with Relationship instances


  • Retrieve relationships elements

A relationship is made of two nodes and the relationship that joins them. The relationships possesses a get() method. This method allows us to retrieve one/two node(s) of a relationship and/or the relationship object. As the get() method of the node_models, this one takes many parameters to allow us to do very complex and customizable requests :

  • direction (optional, default="bi") : Must be "from", "to", or "bi". If it is "from", the research will be focused on all the relationships that have, as a start point, the self node_model's instance. If it is "to", the research will be focused on all the relationships that have, as end point, the self node_model's instance. Finally, if it is "bi", the research will be focused on the relationships of both cases.

  • returned (optional, default="node") : Must be "rel", "node" or "both". If it is "rel", the method will return a list that contains relationships as RelationshipInstance (or of one of its children classes) instances. If it is "node", the method will return a list that contains the nodes at the other ends of these relationships as node_models' instances. Finally if it is "both", it will return a list of dictionaries in which the "rel" key refers to a relationships and the "node" key to its associated node.
    Example :
{"rel": <RelationshipInstance object(uuid="3a43238c76ec4d6cb392b138f0871e75")>,
"node": <Human object(uuid="ec04770e5c8b428d9d94678c3666d312")>}


  • order_by (optional, default=None) : Must be the name of the property with which the returned datas will be sorted. BUT, if self.returned = "both", two different types of datas will be returned (relationships and nodes). Therefore, to sort them, this property must start with "r." (like 'relationships') or "n." (like 'nodes').
    Examples : "r.datetime", "n.first_name", etc...

  • limit (optional, default=None) : Must be an integer. This parameter defines the number of returned elements.

  • skip (optional, default=None) : Must be an integer. This parameter defines the number of skipped elements. For example if self.skip = 3, the first 3 returned elements will be skipped.

  • desc (optional, default=False): Must be a boolean. If it's False the elements will be returned in an increasing order, but if it's True, they will be returned in a descending order.

  • distinct (optional, default=False) : Must be a boolean. If it's True, the returned list will be only composed by single elements.

  • only (optional, default=None) : Must be a list of field_names. If this parameter is filled, the return won't be node_models and relationships instances, but a dict with "only" the mentioned fields. BUT, if self.returned = "both", two different types of datas will be returned (relationships and nodes). Therefore, to mention their properties fields, the elements of the list must start with "r." (like 'relationships') or "n." (like 'nodes').
    Examples : "r.datetime", "n.first_name", etc...

  • filter (optional, default=None) : Must be Q statement. You must use the Q class stored in bulb.db
    Example: Q(name__contains="al") | Q(age__year__lte=8)

  • return_query (optional, default=False) : Must be a boolean. If true, the method will only return the cypher query.

Keep in mind the previous example where we've defined the RelatedAuthorsRelationship and read this demonstration :

node_models.py

from bulb.db import node_models
from bulb.contrib.auth.node_models import User
import datetime

class RelatedAuthorsRelationship(node_models.Relationship):
    rel_type = "WROTE"
    direction = "to"
    start = "User"
    target = "self"
    on_delete = "CASCADE"


class Article(node_models.Node):
    title = node_models.Property(required=True,
                                 unique=True)
    content = node_models.Property(required=True)
    publication_datetime = node_models.Property(default=datetime.datetime.now)
    authors = RelatedAuthorsRelationship()
first_article = Article.create(title="A great article !",
                               content="Lorem ipsum...")

john = User.get(email="john@mail.com")

# Add john as author of the first article
first_article.authors.add(john)

# Retrieve nodes from the other side of the relationship :
first_article.authors.get()
>>> [<User object(first_name="John", last_name="Doe", uuid="20b4ab050cb141868c8cd39dad4d9db2")>]

# Also return the relationships :
first_article.authors.get(returned="both")
>>> [{'rel': <RelatedAuthorsRelationshipInstance object(uuid="db087a94fda54427bf3ef52de425e301")>, 'node': <User object(first_name="John", last_name="Doe", uuid="20b4ab050cb141868c8cd39dad4d9db2")>}]



  • Make your own getter, setter and deletter

A Relationship instance possesses native getter, setter and deletter, but these methods could be personalized for each Relationship class. Handling perfectly every situations is impossible, and trying it leads to very heavy and inefficient programs. To make your own node methods, you'll just have to take the native methods and re-implement them with your modifications.



  • Work with RelationshipInstance instances


    We've just seen how to work with Relationship instances. As a reminder, the Relationship class allow us to create relationships models and then create relationships in the database that respects these models. But you have to know that another class exists to represent the relationships itself in the database, it is called RelationshipInstance. For example, if you use the get() method of the Relationship instances, you can retrieve both nodes and relationships by settings returned parameter on 'both'.
    Let's see a demonstration which is based on the example of the get() method of the Relationship instances (above) :

node_models.py

from bulb.db import node_models
from bulb.contrib.auth.node_models import User
import datetime

class RelatedAuthorsRelationship(node_models.Relationship):
    rel_type = "WROTE"
    direction = "to"
    start = "User"
    target = "self"
    on_delete = "CASCADE"


class Article(node_models.Node):
    title = node_models.Property(required=True,
                                 unique=True)
    content = node_models.Property(required=True)
    publication_datetime = node_models.Property(default=datetime.datetime.now)
    authors = RelatedAuthorsRelationship()

first_article = Article.create(title="A great article !",
                               content="Lorem ipsum...")

john = User.get(email="john@mail.com")

# Add john as author of the first article
first_article.authors.add(john)

# Retrieve nodes and relationships
first_article.authors.get(returned="both")
>>> [{'rel': <RelatedAuthorsRelationshipInstance object(uuid="db087a94fda54427bf3ef52de425e301")>, 'node': <User object(first_name="John", last_name="Doe", uuid="20b4ab050cb141868c8cd39dad4d9db2")>}]

You can see a RelatedAuthorsRelationshipInstance instance. The RelatedAuthorsRelationshipInstance class inherits from the RelationshipInstance class. Indeed, bulb automatically create children of the RelationshipInstance class based on your relationships models (classes that inherits from the Relationship class). And if you use the "all in node_models" syntax, the RelationshipInstance class will be used.



  • Update relationships' properties


Now that you have retrieved a RelatedAuthorsRelationshipInstance instance you can update properties of the relationship it represents. To do so, bulb give you an update() method. This method works identically like the update() method of the Node instances : it takes as first argument the name of the property to update and as second argument, the new value of this property.

node_models.py

(...)
import datetime

my_rel_instance.update("creation_datetime", datetime.datetime.now())


  • Delete relationships

RelatedAuthorsRelationshipInstance instances possess a delete() method, that allows us to delete the relationship.