Skip to content

Discourse

Matt edited this page Nov 30, 2021 · 1 revision

The simplest case for using Aspen is a simple relationship between two people or things, like:

Liz knows Jack.

Aspen doesn't know which of these are nodes and which are edges, so we have to tell it by adding parentheses () to indicate nodes and square brackets [] to indicate edges. These conventions are intentionally the same as Cypher.

(Liz) [knows] (Jack).

Let's think about what we can conclude from this statement:

  • The strings of text "Liz" and "Jack" are names.
  • Liz and Jack are people, so they should have a :Person label in Cypher.
  • If Liz knows Jack, Jack knows Liz as well, so the relationship "knows" is reciprocal.

However, Aspen doesn't know any of this automatically.

So, we need to tell Aspen:

  • What attribute to assign the text "Liz" and "Jack" (name)
  • What kind of labels to apply to the nodes (Person)
  • That the relationship "knows" is a reciprocal (aka mutual) relationship

Default label

A label is the type or class of object: a Person, an Object, a Car, etc. See Neo4j documentation on Node Labels.

First, we need to tell Aspen that, when it encounters a simple node like (Liz), that it should assume it's a person. (If we don't, it's given the abstract label Entity.)

# Discourse

default:
  label: Person

Default attribute

By default, Aspen assumes that the given attribute is a name. If we wanted to make it first_name, we'd write:

# Discourse

default:
  label: Person
  attribute: first_name

When we run this, we get this Cypher:

MERGE (person_liz:Person { first_name: "Liz" })
MERGE (person_jack:Person { first_name: "Jack" })

MERGE (person_liz)-[:KNOWS]->(person_jack)

Default attributes for other labels

Nodes can be labeled or unlabeled. Unlabeled nodes are given the default label, whereas labeled nodes keep their labels.

Unlabeled node: (Liz) Labeled node: (Show: TGS with Tracy Jordan)

Let's take a look at a narrative with labeled nodes.

(Jack) [works at] (Employer: Kabletown)
(Liz) [works at] (Show: TGS with Tracy Jordan)

Given the following line, what attributes should Kabletown and TGS with Tracy Jordan be set to?

Right now, they would both be assigned to name. If we want to set Kabletown as company_name and TGS as the show's title, we add some more to the discourse.

default:
  # ... lines omitted
  attributes:
    Employer: company_name
    Show: title

Reciprocal relationships

Take a look at the arrow—it's pointing from Liz to Jack, suggesting that Liz knows Jack but Jack doesn't know Liz.

However, we want the relationship "knows" to be reciprocal, because if Liz knows Jack, we can assume that Jack knows Liz.

A note on reciprocal relationships:

In Neo4j, the convention is for reciprocal (also known as bidirectional or undirected) relationships to be represented by a directional relationship. Read more at GraphAware.

However, we want our resulting Cypher to show a reciprocal relationship so we can read the Cypher and understand the intent of the code.

If we wanted to show that we intend to create a reciprocal relationship, we'd write Cypher for "Liz knows Jack" as follows. Notice that there's no arrowhead—the relationship is "undirected".

MERGE (person_liz)-[:KNOWS]-(person_jack)

To get this reciprocality in Aspen, we list all the reciprocal relationships after the keyword reciprocal or mutual:

# Discourse
default:
  label: Person
reciprocal: knows # this would also work with `mutual: knows`
----
# Narrative
(Liz) [knows] (Jack).
MERGE (person_liz:Person { first_name: "Liz" })
MERGE (person_jack:Person { first_name: "Jack" })

MERGE (person_liz)-[:KNOWS]-(person_jack) # Note the absence of pointed arrow caps (e.g. "->")

NOTE: Cypher renders a mutual relationship as undirected, which doesn't meaningfully affect structure or performance, but it's worth noting that other formats differentiate between mutual and undirected relationships. In those formats, mutual relationships point in 2 directions, while Cypher's undirected relationships point in null or 0 directions.

If we had multiple reciprocal relationships, we'd write:

reciprocal: knows, is friends with, is married to

This would ensure that [:KNOWS], [:IS_FRIENDS_WITH], and [:IS_MARRIED_TO] would all be encoded as reciprocal/undirected relationships.

Schema Protections

Writing freeform text using Aspen can easily lead to typos, and typos in your graph model are no fun.

Example: It would be really easy to write a node using the label Persno and not catch it. When you go to query your data for Person nodes, the one with the Persno label won't even show up.

So, Aspen provides Protections that catches typos in node labels and edge names.

For example, to ensure that you can only have Person and Employer nodes, and that the only relationships allowed are knows and works at, write the following in your discourse.

allow_only:
  nodes: Person, Employer
  edges: knows, works at

You can write longer protection lists using list items, like this:

allow_only:
  nodes:
    - Person
    - Employer
  edges:
    - knows
    - works at

If you make a typo in your narrative, or try to use a label that isn't Person or Employer, you'll get an error.

Documentation

Quickstart Guide

Clone this wiki locally