Report bug
What is the current behavior?
Model includes() does not works as indexOf(). As define here: Includes ES6
If the current behavior is a bug, please provide the steps to reproduce.
var User = new Schema({
name:{
type: String,
default: ''
}
});
var relationSchema = new Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
friends: [ { type: mongoose.Schema.Types.ObjectId, ref: 'User' } ]
});
User:
{
"_id" : ObjectId("5acd4da4c271813c48bcd843"),
"name" : "Alice"
},
{
"_id" : ObjectId("5ad4eb200a67063840c158d5"),
"name" : "Bob"
}
Relation:
{
"_id" : ObjectId("5acd7fa7f7be5030fc626dd0"),
"friends" : [
ObjectId("5ad4eb200a67063840c158d5")
],
"user" : ObjectId("5acd4da4c271813c48bcd843"),
}
let userId= "5acd4da4c271813c48bcd843";
let friendId= "5ad4eb200a67063840c158d5";
//Relation contains the friendId in friends
Relations.findOne({user: userId})
.then((rel) => {
consloe.log(rel.friends.includes(friendId)); // false
console.log(rel.friends.indexOf(friendId)); //0
What is the expected behavior?
indexOf and includes must return the same results
Please mention your node.js, mongoose and MongoDB version.
Node v8.9.1
Mongoose v5.0.14
MongoDb v3.4.4
Hi @mapo-job,
When you call .indexOf()
on a document array (rel.friends), you are calling a method defined by mongoose on the MongooseArray type.
When you call .includes()
on that same document array, you are calling the js Array.prototype.includes()
(there is no .includes()
defined on MongooseArray).
The difference between the two, is that the MongooseArray.indexOf()
uses coercion to compare each element as a string. Hence the .indexOf()
works, and includes doesn't.
With Array.prototype.includes()
from your example, you are comparing the id string to a BSON ObjectId.
Here is an example that illustrates this.
#!/usr/bin/env node
'use strict';
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
var userSchema = new Schema({
name: String
});
var relationSchema = new Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
friends: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}]
});
const User = mongoose.model('User', userSchema);
const Relation = mongoose.model('Relation', relationSchema);
const user1 = new User({ name: 'billy' });
const user2 = new User({ name: 'sarah' });
const user3 = new User({ name: 'isaac'});
const relation = new Relation({
user: user3._id,
friends: [user2._id, user1._id]
});
console.log(relation.friends.indexOf(user1.id));
console.log(relation.friends.includes(user1._id)); // note the use of _id
issues: ./6354.js
1
true
issues:
By using the BSON ObjectId with Array.prototype.includes()
it works as expected.
@vkarpov15 Would adding MongooseArray.includes()
with built-in string coercion at this point be ok? Or could we add an equivalent method with a different name sooner, like .$includes()
, or .has()
maybe?
My gut reaction was that adding .includes()
now would break stuff for folks relying on the Array.prototype.includes()
already, but after some reflection, if we're coercing both sides to a string, it probably wouldn't.
Hi @lineus,
Thanks for the clarification, I understand that adding MongooseArray.includes()
would be a backward compatibility issue and prefer if it's in a major release. Until then, I'll use indexOf...
I try with ObjectID but the object is not the same so it does not work.
console.log(relation.friends.indexOf(user1.id)); //1
console.log(relation.friends.indexOf(new ObjectID(user1.id))); //1
console.log(relation.friends.includes(user1._id)); // true note the use of _id
console.log(relation.friends.includes(new ObjectID(user1.id))); //false
console.log(relation.friends.includes(new mongoose.Types.ObjectId(user1.id))); //false
just ran into this issue..
I agree adding MongooseArray.prototype.includes as a new feature would be great since its an expected behaviour.
Most other Array methods are implemented in MongooseArray so it can be very confusing to figure out why includes doesn't work.
I just ran into similar problem
rel.friends.includes(friendId)
is comparing a String to Objetcs, so using Array.prototype.includes() shouldn't work
However, the code below should work.
console.log(JSON.stringify(rel.friends).includes(friendId)) // true
I added a pull request: https://github.com/Automattic/mongoose/pull/7732
I noticed locally listofObjectIds.includes(user._id) worked but in a Heroku environment .includes
array prototype did not work the same, so locally it was returning true, but on Heroku same code was returning false.
I ended up mapping to string, but perhaps I'll change to indexOf.
const isFriend = friends.map(f => f._id.toString()).includes(user._id.toString());
@magbicaleman are you sure you were running the same version of Mongoose locally and on Heroku? Which version?
Most helpful comment
I just ran into similar problem
rel.friends.includes(friendId)
is comparing a String to Objetcs, so using Array.prototype.includes() shouldn't workHowever, the code below should work.
console.log(JSON.stringify(rel.friends).includes(friendId)) // true