Relationships
A data model can include object field types, that is, fields that are set to object values. In Dari, there are two types of relationships between objects: referenced (non-embedded) or embedded.
In a referenced relationship, a referencing object stores a reference ID to another object. The referenced object is stored as another record in the underlying database.
The following example shows an Activity
object that’s represented in JSON. The project
field references another object.
psddev.dari.test.Activity: 0000015a-dc72-dcb9-af7b-fdfac06c0000
{
"project" : {
"_ref" : "0000015a-dc72-dcb9-af7b-fdfac0550000",
"_type" : "0000015a-7bb5-d284-addf-7ff7e7c00001"
},
"activityDate" : 1492833600000,
"activityType" : "Download RFP response",
"_id" : "0000015a-dc72-dcb9-af7b-fdfac06c0000",
"_type" : "0000015a-7bb5-d284-addf-7ff7e7c00000"
}
In an embedded relationship, a containing object stores another object within it. The embedded object only exists with the containing object. It does not exist as a separate record in the database.
In the following example, the value of the project
object field is embedded in the Activity
object.
psddev.dari.test.Activity: 0000015a-d91b-d454-ad5b-ffbf95100000
{
"project" : {
"code" : "bethany-47-k528",
"desc" : "Evaluate brand message",
"_id" : "0000015a-d91b-d454-ad5b-ffbf95170000",
"_type" : "0000015a-7bb5-d284-addf-7ff7e7c00001"
},
"activityDate" : 1490673600000,
"activityType" : "Checkout",
"_id" : "0000015a-d91b-d454-ad5b-ffbf95100000",
"_type" : "0000015a-7bb5-d284-addf-7ff7e7c00000"
}
Any object that extends Record can be referenced by another object. As objects stored as separate records in the database, referenced objects can be directly queried and retrieved from the database.
In the following example, an Activity
object is created. Because the Project
object is referenced, it is created and saved first, then set on the project
field of the Activity
object.
import com.psddev.dari.db.*;
import psddev.dari.test.*;
import java.util.*;
import java.text.*;
public class Code {
public static Object main() throws Throwable {
Activity activity = new Activity();
String startDateString = "04/22/2017";
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
activity.setActivityDate(df.parse(startDateString));
activity.setActivityType("Download RFP response");
Project project = new Project();
project.setCode("tilden-21-x439");
project.setDesc("Customer satisfaction survey");
project.save();
activity.setProject(project);
activity.save();
/* Returns new object in JSON */
return activity;
}
}
You can query the Project
class to retrieve the object.
import com.psddev.dari.db.*;
import psddev.dari.test.*;
import java.util.*;
public class Code {
public static Object main() throws Throwable {
Project project = Query.from(Project.class)
.where("code = 'tilden-21-x439'").first();
return project;
}
}
Dari objects that do not extend Record
cannot be persisted as database records. For example, the Dari StorageItem
, Location
, and Region
classes do not inherit from Record
. Instances of these classes cannot be saved as independent objects in the database. They can only exist as dependent objects embedded within a containing object that inherits from Record
.
Embedded objects cannot be directly saved or queried. They are saved when the containing object is saved. To retrieve embedded objects, you must query the containing class, then get the embedded objects from the containing object’s field values.
Using the @Embedded
annotation, you can optionally embed a Record
-derived object into a containing object. For example, assuming that the Project
class extends Record
, an Activity
object stores—by default—a reference to a Project
object. But if the project
field is set to embedded in the Activity
class, then a Project
object that is set on the field is embedded into the Activity
object.
public class Activity extends Record {
@Embedded
private Project project;
}
In the following example, an Activity
object is created with an embedded Project
object. The Project
object is not saved separately, but is saved as part of the Activity
object.
import com.psddev.dari.db.*;
import psddev.dari.test.*;
import java.util.*;
import java.text.*;
public class Code {
public static Object main() throws Throwable {
Activity activity = new Activity();
String startDateString = "03/28/2017";
DateFormat df = new SimpleDateFormat("MM/dd/yyyy");
activity.setActivityDate(df.parse(startDateString));
activity.setUser(psddev.dari.test.User.getUser("Curly") );
activity.setActivityType("Checkout");
Project project = new Project();
project.setCode("bethany-47-k528");
project.setDesc("Evaluate brand message");
activity.setProject(project);
activity.save();
/* Returns new object in JSON */
return activity;
}
}
Because the Project
object is stored in the Activity
record, you cannot query the Project
class to retrieve the object. Instead, you must query the Activity
class to get the embedded Project
object.
import com.psddev.dari.db.*;
import psddev.dari.test.*;
import java.util.*;
public class Code {
public static Object main() throws Throwable {
for (Activity activity : Query.from(Activity.class).selectAll() ) {
if (activity.getProject() == null) continue;
if (activity.getProject().getCode().equals("bethany-47-k528") ) {
Project project = activity.getProject();
return project;
}
}
return ("Can't find bethany-47-k528");
}
}
You can model a many-to-many relationship in your Java classes. For example, a many-to-many relationship can be modeled between a Video
class and a Playlist
class. The Video
class represents a single video, and the Playlist
class represents a single playlist. Because a Playlist
object can have a collection of videos, a Video
object can be referenced by many Playlist
objects.
The following snippets show this relationship.
public class Video extends Record {
@Indexed
private String title;
@MimeTypes("+video/")
private StorageItem video;
/* Getters and setters */
}
public class Playlist extends Record {
@Indexed
private String owner;
@Indexed
private String name;
@Indexed
private List<Video> videos;
/* Getters and setters */
}
Directly referencing a list of related objects can work for a relatively small number of items where minimal information is stored about the relationship. But such a model is unworkable when dealing with collections of thousands of items, and when more information is required than what can be captured in two model classes. To model a more advanced many-to-many relationship, use an intersection class.
To continue with the playlist/video scenario, let’s introduce additional requirements. An individual video or a playlist can only be represented by one Video
or Playlist
object. And a video must have a set position (order) within a playlist. To accommodate these requirements, an intersection class can be used to express the relationship between the Video
and Playlist
classes.
As shown in the schema diagram rendered by the Database Schema Viewer, the PlaylistItem
class serves as the intersection class. Each PlaylistItem
object represents a pairing of a single Video
object and a single Playlist
object, indicating that a user’s playlist includes that video at a set position.
The following snippets show how the above schema is modeled in code. A Video
object has a collection field that references all of the PlaylistItem
objects associated with the video. Similarly, a Playlist
object has a collection field that references all of the PlaylistItem
objects associated with the playlist.
public class Video extends Record {
@Indexed
private String title;
@MimeTypes("+video/")
private StorageItem video;
@Indexed
private List<PlaylistItem> playlists;
/* Getters and setters */
}
public class Playlist extends Record {
@Indexed
private String owner;
@Indexed
private String name;
@Indexed
private List<PlaylistItem> videos;
/* Getters and setters */
}
public class PlaylistItem extends Record{
@Indexed
@Required
private Playlist playlist;
@Indexed
@Required
private Video video;
@Indexed
@Required
private double position;
/* Getters and setters */
/* Ensures that a video can only be in a playlist one time */
@Indexed (unique = true)
public String getUniqueKey() {
return this.getPlaylist().getId().toString() + "_" +
this.getVideo().getId().toString();
}
}
A common search that would be used in the playlist/video scenario is to query for a playlist and show all of the videos referenced by the playlist:
/* Get a playlist */
Playlist pl = Query.from(Playlist.class).first();
/* Get all videos associated with the playlist sorted by position */
List<PlaylistItem> items = Query.from(PlaylistItem.class)
.where("playlist = ?", playlist)
.sortAscending("position")
.selectAll();
for (PlaylistItem item : items) {
System.out.println("Title: " + item.getVideo().getTitle());
}