Uploaded image for project: 'Realm JavaScript SDK'
  1. Realm JavaScript SDK
  2. RJS-2741

Improve duplication of realm entities and upsert with relationships

      Problem

      Hi, I will try to expose the improvements with a practical example.

      Domain example

      I will obfuscate my own domain and will use an imaginary one for privacy reasons.

      We will have a FlightTrip entity containing a list of embedded FlightTripLeg. FlightTripLeg also contains a relationship with a list of Airport (many to one relationship) and an embedded list of "TripCustomConfiguration".

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      const FLIGHT_TRIP_SCHEMA: Realm.ObjectSchema = {
        name: "FlightTrip",
        primaryKey: "_id",
        properties: {
          _id: {
            type: "objectId",
            default: () => new Realm.BSON.ObjectID(),
          },
          legs: { type: "list", objectType: "FlightTripLeg" },
          owner: "string",
          createdAt: "date",
          updatedAt: "date",
        },
      }
      
      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      const FLIGHT_TRIP_LEG_SCHEMA: Realm.ObjectSchema = {
        name: "FlightTripLeg",
        embedded: true,
        properties: {
          _id: {
            type: "objectId",
            default: () => new Realm.BSON.ObjectId(),
          },
          name: "string",
          departureDate: "date",
          arrivalDate: "date",
          airports: { type: "list", objectType: "Airport" },
          customConfigurations: { type: "list", objectType: "TripCustomConfiguration" },
        },
      };
      
      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      const AIRPORT_SCHEMA: Realm.ObjectSchema = {
        name: "Airport",
        primaryKey: "_id",
        properties: {
          _id: {
            type: "objectId",
            default: () => new Realm.BSON.ObjectId(),
          },
          name: "string",
          createdAt: "date",
          updatedAt: "date",
        },
      }
      
      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      const TRIP_CUSTOM_CONFIGURATION_SCHEMA: Realm.ObjectSchema = {
        name: "TripCustomConfiguration",
        embedded: true,
        properties: {
          _id: {
            type: "objectId",
            default: () => new Realm.BSON.ObjectId(),
          },
          service: "string",
          price: "int"
        },
      }
      

      Duplication problem

      The ideal way to duplicate a FlightTrip would be the following, but at the moment it just creates a new FlightTrip with an empty legs list (so I suppose this is a problem with embedded objects).

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      function duplicateFlightTrip(flightTripId: Realm.BSON.ObjectId) {
        const flightTrip = realm.objectForPrimaryKey(
          FLIGHT_TRIP_SCHEMA.name,
          flightTripId
        );
        realm.write(() => {
          realm.create(FLIGHT_TRIP_SCHEMA.name, {
            ...flightTrip, // This is a shallow copy, so we are reusing the same flightTrip.legs embedded list
            _id: new Realm.BSON.ObjectId(),
          });
        });
      }
      

      > Why not using flightTrip.toJSON()? Because converting to JSON would also affect the relationships and in this case we have a relationship with airport. Using toJSON() would fail saying that the airport entities are already created (same primary key).

      Workaround

      Remapping all the embedded objects does indeed work as expected, it creates a duplicate FlightTrip with all the embedded objects and relations!!!

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      function duplicateFlightTrip(flightTripId: Realm.BSON.ObjectId) {
        const flightTrip = realm.objectForPrimaryKey(
          FLIGHT_TRIP_SCHEMA.name,
          flightTripId
        );
        realm.write(() => {
          realm.create(FLIGHT_TRIP_SCHEMA.name, {
            ...flightTrip,
            _id: new Realm.BSON.ObjectId(),
            legs: flightTrip.legs.map((leg) => {
              return {
                ...leg,
                customConfigurations: leg.customConfigurations(
                  (customConfiguration) => customConfiguration
                ),
              };
            }),
          });
        });
      }
      

      Upsert

      Imagine we want to make some modifications to our flight trip and save all of them when the user presses SAVE button, we cannot use the strategy where we update everything at the moment when the user changes its value.

      To do this, we need first to create a copy of the realm entity so that we can save modifications over the time (we cannot modify a live entity outside of a realm transaction) and finally upserting the entity to save all the modifications.

      In this case we have to do the same as the last case + remap also the Airport relationship, not only the embedded objects

      Unable to find source-code formatter for language: typescript. Available languages are: actionscript, ada, applescript, bash, c, c#, c++, cpp, css, erlang, go, groovy, haskell, html, java, javascript, js, json, lua, none, nyan, objc, perl, php, python, r, rainbow, ruby, scala, sh, sql, swift, visualbasic, xml, yaml
      const flightTripCopy = {
        ...flightTrip,
        _id: new Realm.BSON.ObjectId(),
        legs: flightTrip.legs.map((leg) => {
          return {
            ...leg,
            airports: leg.airports((airport) => airport),
            customConfigurations: leg.customConfigurations(
              (customConfiguration) => customConfiguration
            ),
          };
        }),
      };
      
      // Make modifications
      
      function save() {
        realm.write(() => {
          realm.create(
            FLIGHT_TRIP_SCHEMA.name,
            flightTripCopy,
            Realm.UpdateMode.Modified
          );
        });
      }
      

      > Why preserving the relation objects when doing an upsert? You can upsert the relations too!

      The answer to this is a bit complex, imagine that you are using Flexible Sync and the actual user does not have the permissions to write to airport collection, if he tries to also upsert the relations a sync error will happen. It seems that maintaining the original relation objects relam is capable of knowing that they don't need to be upserted.

      Solution

      Maybe adding something to the API like flightTrip.unmanaged() that returns a copy of the object remapping its embedded objects and relationships while also preserving the original objects inside the relationship?

      Alternatives

      Improve the docs

      How important is this improvement for you?

      Would be a major improvement

      Feature would mainly be used with

      Atlas Device Sync

            Assignee:
            Unassigned Unassigned
            Reporter:
            unitosyncbot Unito Sync Bot
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: