Class Diagrams & Data Modeling

The entire documentation for neoads is summarised in the following diagram: (click on the figure to enlarge)

The abstract data structures described above were originally built to support functionality beyond the typical operations described by the theory of abstract data types.

For example, the abstract data structures can hold pointers to any kind of an arbitrary data model as described by neomodel objects. They can also be initialised to their default values via queries that minimise the amount of data that are exchanged between the server and the client.

Some of this functionality will be presented here through minimal examples.

Interested readers are welcome to dive deeper into the more detailed descriptions of the data types and their theory that is available elsewhere in this documentation to deal with more complex use cases.

Abstract data structures over arbitrary data models

All the abstract data structures offered by the “core” neoads can point to arbitrary content as long as that content descends from a particular neoads entity, called ElementDomain. This “content” can be as complex as it is required by a given domain.

In the original project that motivated its development, neoads supports a data model in excess of 30 entities with complex relationships between them (including inheritance).

The smallest demonstration here will re-use a scenario that has been done to exhaustion in Neo4j examples:

A Person related to another Person living in some Country.

This narrative is captured in the following data model:

class PersonalRelationship(neomodel.StructredRel):
    """
    A very simple assocation class between entities of type Person that bears the date the
    acquaintance was made.
    """
    on_date = neomodel.DateTimeProperty(default_now=True)

class Country(neoads.ElementDomain):
    uid = neomodel.UniqueIdProperty()
    name = neomodel.StringProperty()

class Person(neoads.ElementDomain):
    uid = neomodel.UniqueIdProperty()
    full_name = neomodel.StringProperty()
    acquainted_with = neomodel.RelationshipTo("Person",
                                              "ACQUAINTED_WITH",
                                              model = PersonalRelationship)
    lives_in = neomodel.RelationshipTo("Country", "LIVES_IN")

The important point to notice here is that any entity that might be needed to be stored in some abstract data structure, must derive from ElementDomain.

In the above example, we anticipate that for a given use case, we might need to create AbstractSet, AbstractMap or AbstractDLList of Person, Country entities.

From this point onwards, the examples assume that a Neo4J instance is available and that it contains data tha conform to this minimal data model.

Initialising lists via queries: The direct way

Suppose now that we have a need to create a (doubly linked) list of Person entities that live within the EU27 geopolitical region.

With neoads, this can be achieved via a simple initialisation-by-query call, as follows:

# First of all create the list

some_abstract_list = neoads.AbstractDLList(name="EU_27_PERSONS").save()


# Then populate it

some_abstract_list.from_query("MATCH (ListItem:Person)-[LIVES_IN]->(b:Country) "
                              "WHERE b.name IN ['Austria', 'Belgium', 'Bulgaria', 'Croatia', 'Cyprus', 'Czechia', "
                              "'Denmark', 'Estonia', 'Finland', 'France', 'Germany', 'Greece', 'Hungary', "
                              "'Ireland', 'Italy', 'Latvia', 'Lithuania', 'Luxembourg', 'Malta', 'Netherlands', "
                              "'Poland', 'Portugal', 'Romania', 'Slovakia', 'Slovenia', 'Spain', 'Sweden'] ")

Notice here that from_query(), accepts an incomplete, READ type CYPHER query that must have binded one of its variables to the graph entity that will constitute the content of the doubly linked list.

This binding must specifically be called ``ListItem``.

In one phrase, what this query says is “Run a CYPHER query and build a doubly linked list, the nodes of which point to the query’s results”, provided here that these results are single entities of course.

In a similar way it is also possible to initialise a neoads.AbstractMap via its from_keyvalue_node_query() method.

neoads data structures are interoperable

neoads abstract data structures can actually point to any PersistentElement entity, including themselves, because they also descend from PersistentElement.

Therefore, neoads abstract data structures can contain abstract data structures that contain abstract data structures…ad infinitum.

This means that it is possible to piece together any conceivable combination such as an abstract list of abstract lists of abstract maps between strings and lists of sets of arbitrary data model entities and traverse this Voltron data structure with something like:

if my_entity in u[0][1][ComplexString("Something").save()][9]:
    # Do something
    pass

Notice here that u is the neoads abstract list whose [0] accessor returns a neoads abstract list, whose [1] accessor returns a neoads mapping, whose [ComplexString("Something").save()] accessor returns a neoads abstract list, whose [9] accessor returns a neoads abstrac set whose __contains__ operation is called to determine if it contains some arbitrary data model entity my_entity.

For a more manageable practical example, here is a list of lists, which can be seen as a two dimensional array:

import random


# This will be a list of 10 "rows" holding lists of 20 "columns" of SimpleNumber type elements.

m_rows = 10
n_cols = 20

row_list = neoads.AbstractDLList().save()
for a_row in range(0, m_rows):
    col_list = neoads.AbstractDLList().save()
    [col_list.append(neoads.SimpleNumber(random.random()).save())
     for k in range(0,n_cols)]
    row_list.append(col_list)

This now has initialised row_list as a doubly linked list that points to doubly linked lists that point to SimpleNumber type entities.

We can access any of those via:

print(f"The 5,5 element is {row_list[5][5]}")