rethinkdbreqlnosql

Query to update nested array item without knowing the index


I have a table that contains complex nested documents. The structure of each document is similar to the following example:

{
  "description": "Sample Document",
  "events": [
    {
      "liquids": { ... },
      "protocol": {
        "id": "5b190c4d-6590-49a3-b9a7-69f036f45e55"
      },
      "schedule": { ... }
    },
    {
      "liquids": { ... },
      "protocol": {
        "id": "a4353a88-6021-418f-a18c-0526550edb57",
        "overrides": [
          {
            index: 9,
            values:  {
              foo: "bar"
            }
          {
            index: 7,
            parentIndex: 10,
            values: {
              foo: 8,
              bar: 9
            }
          }
        ]
      },
      schedule: { ... }
    }
  ],
  "id": "7c424fc3-1a58-4a4e-b1b6-8d25412a85da",
  "name": "Sample Doc",
  "project_id": "__ROOT__"
}

I want to update one of the items in the protocol.overrides property of the second item in the events array. I know ahead of time that I need to access myTable.get($id).events(1)('protocol')('overrides') but I have no way of knowing ahead of time which item in the nested array that I need to replace.

In this specific example, lets say that I need to replace the item with index: 7, and parentIndex: 10 (the second item) and lets say that I want to change its values property to {foo: "baz", bar: "curley"}.

I don't see any methods in the reQL API that let me do this. changeAt can be used if I know the array index, but I don't see an indexOf function or anything similar that lets me find the index by matching a predicate.


Solution

  • Assuming I understand what exactly you're asking for, including what data is known and what should be queried by it (if not, tweak it), this is how I'd do it:

    myTable.get($id) 
      .update(function(row) {
        return {
          events: row('events').map(function(event) {
            return r.branch(
              event('protocol')('overrides').default([]).filter({index: 7}),
              event.merge({
                protocol: {
                  overrides: event('protocol')('overrides').default([])
                  .map(function(override) {
                    return r.branch(
                      override('index').eq(7),
                      override.merge({
                        values: {
                          foo: "baz",
                          bar: "curley"
                        }
                      }),
                      override
                    )
                  })
                }
              }),
              event
            )
          })
        }
      })
    

    I'm using:

    Not sure this is the best practice, but it seems like it gets the job done.