mfrachet
mfrachet

Reputation: 8922

Mongodb, reducing a list by control

I m building an application in which I use to manage projects. A projects looks like the following :

{
  "_id": ObjectId("..."),
  "title": "MySuperProject",
  "files": [
    {
      "title":"My skiing day !",
      "right":[{
        "role":"USER",
        "access":["read"]
      }]
    },
    {
      "title":"My little dog, so cute !",
      "right":[{
        "role":"OTHER",
        "access":["read"]
      }]
    }
  ]
}

We can see two different roles here : USER and OTHER.

When I get the above project with the USER role, I need to have the following representation, without the OTHER file :

{
  "_id": ObjectId("..."),
  "title": "MySuperProject",
  "files": [
    {
      "title":"My skiing day !",
      "right":{
        "role":"USER",
        "access":["read"]
      }
    }]
}

Does it exist a way to reduce a list inside of a document based on query or should I make it manually on the result ?

I m working on nodejs and mongoose.

Thanks for your help

EDIT : In fact the right key was an ARRAY

Upvotes: 3

Views: 66

Answers (1)

BatScream
BatScream

Reputation: 19700

This is one of the classic use cases of the $redact stage. You could aggregate it as below:

var role = "USER";
var projectTitle = "MySuperProject";

db.t.aggregate([
  {
    $match: {
      "title":projectTitle
    }
  },
  {
    $redact: {
      $cond: [{
        $eq: [role, {
          $ifNull: ["$role", role]
        }]
      }, "$$DESCEND", "$$PRUNE"]
    }
  }
])

output:

{
        "_id" : 1,
        "title" : "MySuperProject",
        "files" : [
                {
                        "title" : "My skiing day !",
                        "right" : [
                                {
                                        "role" : "USER",
                                        "access" : [
                                                "read"
                                        ]
                                }
                        ]
                },
                {
                        "title" : "My little dog, so cute !",
                        "right" : [ ]
                }
        ]
}

At each level, a document is evaluated, only if the document at the particular level, returns true to the $cond presented by the $redact stage, we $$DESCEND into its sub-documents, else $$PRUNE.

It would list you all the files per project, and an array of access roles for each file. If you would want to exclude files that the "user" has no rights on, you could $redact again:

db.t.aggregate([
  {
    $match: {
      "title": projectTitle
    }
  },
  {
    $redact: {
      $cond: [{
        $eq: [role, {
          $ifNull: ["$role", role]
        }]
      }, "$$DESCEND", "$$PRUNE"]
    }
  },
  {
    $redact: {
      $cond: [{
        $gt: [{
          $size: {
            $ifNull: ["$right", [1]]
          }
        }, 0]
      }, "$$DESCEND", "$$PRUNE"]
    }
  },
])

output:

{
        "_id" : 1,
        "title" : "MySuperProject",
        "files" : [
                {
                        "title" : "My skiing day !",
                        "right" : [
                                {
                                        "role" : "USER",
                                        "access" : [
                                                "read"
                                        ]
                                }
                        ]
                }
        ]
}

The above approach avoids the costly $unwind stage. It is always advisable to take different approaches and see which one suits you the best.

Upvotes: 4

Related Questions