Subject Classes
One of the first use cases of Social DNA is for Neigbourhoods to be able to agree that a certain collection of links represent one thing.
Consider the following example for Todo definition:
@SDNAClass({
name: "Todo",
})
export default class Todo {
@SubjectProperty({
through: "rdf://title",
writable: true,
required: true,
resolveLanguage: "literal",
value: ""
})
title: string;
@SubjectProperty({
through: "rdf://done",
writable: true,
required: true,
resolveLanguage: "literal",
value: false,
})
isDone: boolean;
}
It defines a Subject Class called "Todo" with two properties: "state" and "title". The used predicates "subject_class", "property" and "property_getter" etc. enable a generic way for ADAM and UIs to interface with these classes and reflect on their properties. We've developed tooling around Subject Classes that is designed to make it seamless for app developers to leverage this power. With these tools, developers can define and interact with their app-specific ontology based on ADAM's meta-ontology easily, thus ensuring interoperability.
The concept of Subject Classes shares a close relationship with SHACL shapes, a standard for validating RDF graphs against a set of conditions. Just like SHACL shapes, Subject Classes allow for the definition of triple-based associations which get grouped to form higher-level virtual objects, or "subjects". Tooling for compatibility with SHACL is not implemented yet, but planned.
This approach provides an ergonomic way for developers to define complex and meaningful relationships within their data.
Learning prolog to define structure and rules to create SDNA's can be diffcult thats why a4dm comes with decorators that helps convert a class to a SDNA class, there are two ways they can be used Repository & Record implementation.
Decorators
Decorators are needed to create sdna class but not if the snda is already created (NOTE: class signature should match for it to fetch the snda).
There are 4 Decorators that are needed to create Subject classes.
SDNAClass
Every class must be Decorated using the decorator inorder for it to have the correct subject name.
Properties:
Name: Name of the Subject class (When the js class goes through a minfiner it can change the class name so needed this)
SubjectProperty
All the properties on the subject class that are not collections should use this decorator to define the property. Atleast one property should be an instance property for the class to function meaning it should define through, initial & required.
Properties:
- through: Indicates the predicate for the property
- initial: Initial value for the property (needed to declare the property as a instance/ flag property)
- required: Is the property required (needed to declare the property as a instance/ flag property)
- writable: Is this property writable, also should a setter be generated for this property.
- resolveLanguage: What language should this property use to store itself, defaults to literal language.
- getter: Overrides the default getter, a prolog query.
- setter: Overrides the default setter, a prolog query.
SubjectFlag
This decorator simplifies the creation of an instance property. Unlike SubjectProperty this is not writable mean once define can't be changed so if you are planning to add an instance property that can be overridden use SubjectProperty.
through: Indicates the predicate for the property value: Initial value for the property
SubjectCollection
This decorator can be used to define collections/ arrays and comes with some additional functions unlike SubjectProperty. With this decorator you can set, push & remove items from the collections.
through: Indicates the predicate for the property where: isInstance: You can pass in the subject class directly instead of using a prolog query. condition: prolog query to add a condition
Vanilla JS way
The above implementation can be writen below as js class using the decorator.
@SDNAClass({ name: "Todo" })
class Todo {
@SubjectFlag({
through: "todo://state",
initial: "todo://ready",
})
state: string = "";
@SubjectProperty({
through: "todo://has_title",
writable: true,
resolveLanguage: "literal",
})
title: string = "";
@SubjectCollections({
through: "flux://entry_type",
where: { condition: `triple(Target, "flux://has_reaction", "flux://thumbsup")` }
})
likedMessages: string = "";
}
Generate subject class ():
// this will write the sdna to the perspective
Todo.generateSDNA();
To create a new subject instance:
// this checks if the sdna was written to the perspective or not and throws an error
// Takes two parameter the SubjectClass & a baseExpression this can be any string
await perspective!.createSubject(new Todo(), root)
Get the subject proxy:
// this checks if the subject instance was created or not and throws an error
// Takes two parameter the a baseExpression this can be any string & SubjectClass
await perspective!.getSubjectProxy(root, new Todo())
Check if the instance was created for this subject class
// this checks if the subject instance was created or not and throws an error
// Takes two parameter the a baseExpression this can be any string & SubjectClass
await perspective!.isSubjectInstance(root, new Todo()))
To remove a subject instance:
// this checks if the sdna was written to the perspective or not and throws an error
// Takes two parameter the SubjectClass & a baseExpression this can be any string
await perspective!.removeSubject(new Todo(), root)
Get all subject proxies for this class:
await perspective!.getAllSubjectProxies(new Todo())
Update properties/ collections:
Decorating the class will add new functions for each property & collection that can be used to update them. For property it will create new method with set prefix to the property name. ex. title will add setTitle function & for collection it will add 3 functions ex. ingredient will add setIngredient, addIngredient & removeIngredient..
const todod = await perspective!.getSubjectProxy(root, new Todo())
await todo.setTitle("New title");
Repository implementation (move the subjectRepo flux implementation to ad4m)
Record implementation
If the above way of doing thing seems cumbersome, ad4m also implements an Active record implementation for SubjectClasses, which simplfies the whole process.
All you need is to extend the above class with SubjectEntity.
@SDNAClass({ name: "Todo" })
class Todo extends SubjectEntity {
@SubjectProperty({
through: "todo://state",
initial: "todo://ready",
writable: true,
required: true,
})
state: string = "";
@SubjectProperty({
through: "todo://has_title",
writable: true,
resolveLanguage: "literal",
})
title: string = "";
}
Generate subject class (): This will generate the subject class when you create a new instance of the class automatically so need to call generateSdna anymore.
const todo = new Todo(perspective, baseExpression);
To create a new subject instance:
// this checks if the sdna was written to the perspective or not and throws an error
// Takes two parameter the perspective & a baseExpression this can be any string
const todo = new Todo(perspective, baseExpression);
// Just call save on the todo object to create a new instance
todo.save();
Get the subject proxy:
// this checks if the subject instance was created or not and throws an error
// Takes two parameter the perspective & a baseExpression this can be any string
const todo = new Todo(perspective, baseExpression);
// Just call get on the todo object to get the instance, this will throw an error if an instance is not found.
await todo.get()
To remove a subject instance:
// this checks if the sdna was written to the perspective or not and throws an error
// Takes two parameter the perspective & a baseExpression this can be any string
const todo = new Todo(perspective, baseExpression);
// Just call delete on the todo object to remove it
await todo.delete();
Get all subject proxies for this class:
// This will get all the instances of the subject class
await Todo.all(perspective);
Query subject proxies for this class:
/** This will get all the instances of the subject class
*
* SubjectEntityQueryParam:
* - `size: number`: The number of items/ subject proxies to return per page.
* - `page: number`: Page number starts from 1.
* - `source: string`: The baseExpression that all the instances are linked to, defaults to `ad4m://self`
*/
await Todo.query(perspective, { page: 1, size: 10 });
Update properties/ collections:
const todo = new Todo(perspective, baseExpression);
todo.title = "New Title";
// Just update the properties that you want to change and than call update
await todo.update();
// To update collection you can directly set the property like so
todo.ingredients = ["test1", "test2"];
// or you can use pass in an object
todo.ingredients = {
action: "add", // can also be remove
value: "test3",
};