What you want to do is basically to filter sub-array in mongodb.
There are a lot of similar questions around.
You cannot do it with .find()
but you can achieve this with aggregation.
The easiest way would be to use $filter (available starting from MongoDB v. 3.2)
In your case please try this:
var currentDate = new Date();
var effectiveToCondition = {
$elemMatch: {
$or: [
{effectiveTo: {$gt: currentDate}},
{effectiveTo: null}
]
}
};
db.user.aggregate([
{
$match: {
$and: [
{
$or: [
{roles: {$size: 0}},
{roles: effectiveToCondition}
]
},
{
$or: [
{groups: {$size: 0}},
{groups: effectiveToCondition}
]
}
]
}
},
//Will filter out the records where groups AND rolse are both empty
//Please uncomment if needed
// {
// $match: {
// $or: [
// {'groups.0': {$exists: true}},
// {'roles.0': {$exists: true}}
// ]
// }
// },
{
$project: {
user: '$$ROOT',
filteredRoles: {
$filter: {
input: '$roles',
as: 'role',
cond: {
$or: [
{$gt: ['$$role.effectiveTo', currentDate]},
{$not: {$ifNull: ['$$role.effectiveTo', false]}}
]
}
}
},
filteredGroups: {
$filter: {
input: '$groups',
as: 'group',
cond: {
$or: [
{$gt: ['$$group.effectiveTo', currentDate]},
{$not: {$ifNull: ['$$group.effectiveTo', false]}}
]
}
}
}
}
}]);
You'll have 'user' property with the original document and 2 additional properties: filteredGroups and filteredRoles.