JSdata - Query to match all items in array (MongoDB '$all' keyword)

104 Views Asked by At

I'm using jsdata to create a local cache for an Angular 1.5 web application. I modelled my data with the according relations and everythings works fine. I'm struggeling with a query and I'm not even sure if this will work with js-data at all:

Lets assume I have a collection of tags with an ID and a name

 [
  {
    "id": 1,
    "name": "Tag 1"
  },
  {
    "id": 2,
    "name": "Tag 2"
  }
  ,
  {
    "id": 3,
    "name": "Tag 3"
  }
]

and some posts where tags have been assigned:

[   {
      "id": 1,
      "name": "Post with tags 1 and 2",
      "tags": [1, 2]
    },
    {
      "id": 2,
      "name": "Post with tags 2 and 3",
      "tags": [2,3]
    },
    {
      "id": 3,
      "name": "Post with tag 3",
      "tags": [3]
    }
]

I then define a Tag and Post Datastore and set a hasMany relation on the posts:

relations: {
hasMany: {
  tag: {
    localField: 'embedded_tags',
    localKeys: 'tags'
  }
}

When i query for posts I see my tags appear in embedded_tags, so thats fine. I now would like to filter my posts to a certain set of tags: lets say, i only want to see posts with tags 2 AND 3, which would be post 2.

Obiously this does not work:

var resultISect = postDS.filter({
  where: {
    'tags': {
      'iSectNotEmpty': [1, 2]
    }
  }
})

Also this doesn't (and throws a duplicate key warning of course):

var resultAnd = postDS.filter({
  where: {
    'tags': {
      'contains': 3,
      'contains': 2
    }
  }
})

In MongoDB / Mongoose there is the keyword $all which does exactly this. Is there any way to have a filtering like this in jsData or is this too much of a database task and should be done on the backend - which I would like to avoid, because that's more or less the whole point in having a local cache copy, right?

Plunkr: http://plnkr.co/edit/M44V8js0BtZaK6Xq9CYt?p=preview

2

There are 2 best solutions below

0
On

I added a 'matchAll' function to js-data _utils2.default. This works for now, maybe its of help for someone.

Add this function next to the intersection-function:

// check if array contains all elements queried
function matchAll(arr, elements) {

    if (!arr || !elements) {
        return true;
    }

    for (var i = 0, length = elements.length; i < length; i++) {
        if (contains(arr, elements[i])) {
            continue;
        } else {
            return false;
        }
    }
    return true;
}

Expose this function along with the other ones:

exports.default = {
    ...
    guid: guid,
    intersection: intersection,
    matchAll: matchAll,
    isArray: isArray,
    isBlacklisted: isBlacklisted,
    ...
}

and make it react to a new keyword:

...
} else if (op === 'isectEmpty') {
    expr = !_utils2.default.intersection(val || [], term || []).length;
} else if (op === 'isectNotEmpty') {
    expr = _utils2.default.intersection(val || [], term || []).length;
} else if (op === 'matchAll') {
    expr = _utils2.default.matchAll(val || [], term || []);
} else if (op === 'in') {
...
0
On

'where' can be an array, this should work:

where: [
  {
    tags: {
      contains: 3
    }
  },
  'and'
  {
    tags: {
      contains: 2
    }
  }
]

Taken from here: https://github.com/js-data/js-data/blob/c3fcde1abab910cdf4d39e3fbc42699a3b27c1da/test/unit/query/filter.test.js#L243 (good place to discover all filter features, btw)