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. Note that 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"
}

Object References

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;
  }
}

Embedded Objects

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. Note that 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");

  }
}

Many-to-Many Relationships

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 code 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
   ...
}

Note that 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, we recommend the use of 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.

../../_images/intersection-schema.png

The following code 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());
}

...