4

Similar to Find document with array that contains a specific value, but i'm trying to pull it.

db.getCollection('users').find({'favorites':{$elemMatch:{0:5719}}}, {"favorites.$": 1})

returns this:

{
    "_id" : "FfEj5chmviLdqWh52",
    "favorites" : [ 
        [ 
            5719, 
            "2016-03-21T17:46:01.441Z", 
            "a"
        ]
    ]
}

even after this returned 1:

Meteor.users.update(this.userId, {$pull: {'favorites':{$elemMatch:{0:movieid}}}})
Community
  • 1
  • 1
Cees Timmerman
  • 17,623
  • 11
  • 91
  • 124

2 Answers2

2

It doesn't work because $pull is trying to remove a matching element from the "favorites" array. What you want to do is remove from the "array inside the array" of favorites.

For this you need a positional match to point to the nth inner element, then a very careful $pull expression to actually remove that element:

Meteor.users.update(
  { "favorites": { "$elemMatch": { "$elemMatch": { "$eq": 5719 }  } } },
  { "$pull": { "favorites.$": 5719 } }
)

The "double" $elemMatch with the $eq operator is a bit more expressive than { 0: 5719 } since it is not "locked" into the first position only and is actually looking at the matching value. But you can write it that way if you must, or if you "really mean" to match that value in the first position only.

Note that the "index" returned from the match in the positional $ argument is actually that of the "outer" array. So to pull from the

Of course if there is only ever one nested array element within, the you might as well just write:

  { "$pull": { "favorites.0": 5719 } }

Using the direct "first index" position, since you know the inner array will always be there.

In either case, your object updates correctly:

{
        "_id" : "FfEj5chmviLdqWh52",
        "favorites" : [
                [
                        "2016-03-21T17:46:01.441Z",
                        "a"
                ]
        ]
}

If you are trying to $pull the entire array entry from favorites, then the $eleMatch just needs to be dialed back one element:

Meteor.users.update(
    { "_id": this.userId },
    { "$pull": { "favorites": { "$elemMatch": { "$eq": 5719 } } } }
)

Or even:

Meteor.users.update(
    { "_id": this.userId },
    { "$pull": { "favorites": { "$elemMatch": { "0": 5719 } } } }
)

Noting that:

    { "_id": this.userId },

Is the long form that we generally use as a "query" selector, and especially when we want criteria "other than" the _id of the document. MiniMongo statements require at "least" the _id of the document though.

The rest of the statement has one "less" $elemMatch because the $pull already applies to the array.

That removes the whole matched element from the outer array:

{
        "_id" : "FfEj5chmviLdqWh52",
        "favorites" : []
}
Blakes Seven
  • 49,422
  • 14
  • 129
  • 135
  • Thanks, but i want to remove all (ideally one) favorites with the specified ID (first element of the favorite). – Cees Timmerman Mar 22 '16 at 09:03
  • @CeesTimmerman So how is that not what this answers? And how is that not what you asked? The first element has been removed. – Blakes Seven Mar 22 '16 at 09:30
  • The ID i'm looking for is the first element of the favorite, and that entire favorite should be removed from the favorites array. Your short code didn't remove the favorite (nor the ID within the favorite) and your long code gives me this: `MongoError: The dollar ($) prefixed field '$elemMatch' in 'favorites.$elemMatch' is not valid for storage.` – Cees Timmerman Mar 22 '16 at 10:06
  • The error was from including `this.userId,`- is that safe to leave out? – Cees Timmerman Mar 22 '16 at 10:10
  • @CeesTimmerman Undertstand what you are going for now. Added the correct update and explanation. – Blakes Seven Mar 22 '16 at 11:14
  • Thanks. The "from" in the title shouldn't have been there. – Cees Timmerman Mar 22 '16 at 12:37
0

This is the first code i found that actually works:

Meteor.users.update(Meteor.userId(), {$pull: {favorites: {$in: [i]}}})

Apparently $in does partial matching. It seems safer than the working code from this answer:

Meteor.users.update(
    { "_id": this.userId },
    { "$pull": { "favorites": { "$elemMatch": { "$eq": i } } } }
)
Community
  • 1
  • 1
Cees Timmerman
  • 17,623
  • 11
  • 91
  • 124
  • Why would it be safer? It's actually a pet peeve, as far too many people use `$in` because they "think" it **must** be used when matching on an array. The use case is in fact the reverse, where you supply an "array of arguments" that are possible matches for the property. You are after removing an element based on matching a single condition. I cannot see why an "array" makes this clear or safe. To be the code seems obfiscated. – Blakes Seven Mar 22 '16 at 20:38
  • It seemed that position might be taken into account, but what i understood from the docs it acts the same as your code. – Cees Timmerman Mar 23 '16 at 10:07
  • So why submit an "answer" stating you think it is safer or better in any way? You can't say *"I knew this all along"* when your question makes no such statement. Your question in fact states, *"I don't know how to do this"*, and then someone was nice enough to show you. Misleading title and all, taken into account. – Blakes Seven Mar 23 '16 at 10:13
  • You initially only answered the title and the last bit of code is the same as the non-functional code in my question, taking away my accept. My answer clearly stated the working code. Only after that code did i discover in your long answer that the `$eq` you suggested also works. You still got my upvote for that. – Cees Timmerman Mar 23 '16 at 10:19
  • It is not the same and I **clearly** state the difference ( i.e don't need two `$elemMatch` in a `$pull` since it already acts on an array), and of course it does actually remove the element as described. Anyhow this does seem to be wandering. `$eq` looks for equality of a single value, `$in` is meant for a list of values to test for equality of "either" supplied in the list. So it's just a copy of the code interposing one operator for another. Both work. – Blakes Seven Mar 23 '16 at 10:26