Mongodb Searching in an array of ids using include does not work - javascript

I have this model:
const NeighborSchema = new Schema({
friends: [
{
type: Schema.Types.ObjectId,
ref: "users",
},
],
date: {
type: Date,
default: Date.now,
},
});
module.exports = Neighbor = mongoose.model("neighbor", NeighborSchema);
I am trying to see if a friend exists in friends of all neighbors:
const mongoose = require("mongoose");
const ObjectId = mongoose.Types.ObjectId;
const testIncludes = async () => {
let neighbors = await Neighbor.find();
let friends_ids = [];
neighbors.map((neighbor) => {
const { friends } = neighbor;
friends_ids = [...friends_ids, ...friends];
});
// Returns false for this
const element_to_search = ObjectId("60dcbb29118ea36a4f3ce229");
// Returns false for this
// const element_to_search = "60dcbb29118ea36a4f3ce229";
let is_element_found = friends_ids.includes(element_to_search);
};
// Returns false in both cases
testIncludes();
Even though, element_to_search was taken directly from list of returned friends_ids array, when I try to search it using include, it returns false for some reason, whether I search it as a String or as an ObjectId.
Any idea what's going on?

Array.prototype.includes compares each element against the sample until it finds a match. Objects are considered equal only if they reference the same instance of the class. When you call a constructor const element_to_search = ObjectId("60dcbb29118ea36a4f3ce229"); it creates a new instance which has never been in the array, even if its value is the same.
You need to compare scalars. Strings for example:
friends_ids.map(f => f.toString()).includes("60dcbb29118ea36a4f3ce229");
or cast it strings when you build up the friends_ids at the first place to avoid the extra loop over the array.

Related

How can I list all the records to see if they are duplicated?

I have a problem that I cannot resolve. I have a table in MongoDB, and this is structure:
const shopEconomy = new mongoose.Schema({
guildID: { type: String },
name: { type: String },
value: { type: Number },
description: { type: String },
rolereq: { type: String },
roleadd: { type: String },
roleremove: { type: String },
buyinfo: { type: String }
});
I need to list all names from the table (shopData.name) and then check if the typed name exists in the database. I tried to do something like the one below, but it doesn't work.
const shopData = await shopEconomy.find({ guildID: message.guild.id });
let categories = [];
let data = new Object();
for(const i in shopData){
data += `${shopData[i].name}\n`
categories.push(data)
}
Could someone take a look at this and help me out?
The title of the question does not quite match the description of the question. Given the description, let's assume the typed name is assigned to var typedName.
Let's also assume that you have bound your shopEconomy schema to a model that will actually interact with a mongodb collection called shopData. Then this will iterate all the docs in the shopData:
var found = false;
cursor = db.shopData.find(); // get EVERYTHING
cursor.forEach(function(doc) {
print(doc['name']);
if(doc['name'] == typedName) {
found = true;
}
});
if(found) {
print(typedName,"was found");
}
It is likely that the OP wants to find duplicate name in the collection, for which this pipeline will work:
db.shopData.aggregate([
{$group: {_id: '$name', N:{$sum:1}} },
{$match: {'N':{$gt:1}}}
]);
Part of the issue here comes from the use of a for...in loop which treats shopData as an object and loops over all properties of it. Instead try using a for...of loop which treats shopData as an array and loops over all objects in it.
...
for(const i of shopData) {
data += `${i.name}\n`
...
}
See also this question on for...in vs for...of and this question on JavaScript loops.

Mongoose: Count array elements

I have the following Schema with a array of ObjectIds:
const userSchema = new Schema({
...
article: [{
type: mongoose.Schema.Types.ObjectId,
}],
...
},
I will count the array elements in the example above the result should be 10.
I have tried the following but this doesn't worked for me. The req.query.id is the _id from the user and will filter the specific user with the matching article array.
const userData = User.aggregate(
[
{
$match: {_id: id}
},
{
$project: {article: {$size: '$article'}}
},
]
)
console.log(res.json(userData));
The console.log(article.length) give me currently 0. How can I do this? Is the aggregate function the right choice or is a other way better to count elements of a array?
Not sure why to use aggregate when array of ids is already with user object.
Define articles field as reference:
const {Schema} = mongoose.Schema;
const {Types} = Schema;
const userSchema = new Schema({
...
article: {
type: [Types.ObjectId],
ref: 'Article',
index: true,
},
...
});
// add virtual if You want
userSchema.virtual('articleCount').get(function () {
return this.article.length;
});
and get them using populate:
const user = await User.findById(req.query.id).populate('articles');
console.log(user.article.length);
or simply have array of ids:
const user = await User.findById(req.query.id);
console.log(user.article.length);
make use of virtual field:
const user = await User.findById(req.query.id);
console.log(user.articleCount);
P.S. I use aggregate when I need to do complex post filter logic which in fact is aggregation. Think about it like You have resultset, but You want process resultset on db side to have more specific information which would be ineffective if You would do queries to db inside loop. Like if I need to get users which added specific article by specific day and partition them by hour.

How should i fetch a random field of a mongoose schema?

const myschema = new mongoose.schema({
userID: message.author.id,
BookesPages: {
Fallingforyou: {type: Number, default: '685'},
Intheblack: {type: Number, deafult: '369'}
},
})
this is an example code, how should i increment in any one of them randomly
You can put all of the field names in an array. Then use a function to randomly generate the number of an index in that array. Afterwards, you can use the value of the array at that index as the random key you want to access.
Here's an example:
// just using regular object here, since we can't use mongoose on stack overflow
const myschema = {
userID: "412341234",
BookesPages: {
Fallingforyou: {
type: Number,
default: '685'
},
Intheblack: {
type: Number,
deafult: '369'
}
},
}
// array of schema keys
const schemaKeys = ['userID', 'BookesPages']
// get a random value from values in an array
const getRandomValue = (array) => {
const index = Math.floor(Math.random() * array.length)
return array[index]
}
// get random key
const key = getRandomValue(schemaKeys)
// now you can get the value corresponding to the random key in your schema
console.log(myschema[key])
This won't work on the subdocuments however, like Fallingforyou or Intheblack. You'd have to use recursion to get those.

React object property value being duplicated on .push inside loop

I have a handleCreate function that takes care of taking some user data and inserting it into a database.
Inside the aliasArr.forEach() loop I POST into my DB new user instances for each element in the aliasArr array. This particular code works as expected, if I check the DB, I will find the new members.
After saving the new members, I want to keep the members in the members array so I can pass it along to another function.
For this, I'm doing members.push(memberAttributes); but if I log the contents of members I get the right amount of elements but the alias property value is duplicated (all other properties should have the same value cause they are being added into the same role in a batch operation).
If I have two new users, say: xyz and abc, I get:
[
{alias: "abc", Role: "admin", "grantedBy": "someone"},
{alias: "abc", Role: "admin", "grantedBy": "someone"},
]
Instead of:
[
{alias: "xyz", Role: "admin", "grantedBy": "someone"},
{alias: "abc", Role: "admin", "grantedBy": "someone"},
]
Here's the code:
handleCreate = () => {
const { memberAttributes } = this.state;
const { role, createRoleMember } = this.props;
const roleArr = [];
roleArr.push(role);
const aliasArr = memberAttributes.alias.split(",");
let members = [];
//false hardcoded during debugging.
if (false /* await aliasIsAdmin(memberAttributes.alias, roleArr) */) {
this.setState({ userExists: true });
} else {
memberAttributes["Granted By"] = window.alias;
memberAttributes.Role = role;
memberAttributes.timestamp = Date.now().toString();
this.handleClose();
aliasArr.forEach((currAlias) => {
memberAttributes.alias = currAlias;
console.log("memberAttributes:", memberAttributes);
members.push(memberAttributes);
console.log("members", members);
const marshalledObj = AWS.DynamoDB.Converter.marshall(memberAttributes);
const params = {
TableName: "xxx",
Item: marshalledObj,
};
axios.post(
"https://xxx.execute-api.us-west-2.amazonaws.com/xxx/xxx",
params
);
});
}
createRoleMember(members); //passing members to this function to do more stuff.
};
I'm wondering if this issue is due to memberAttributes being part of the component's state.
The problem here is that you are pushing references to the same object into the array after changing a value within that object. So whenever you make the change to memberAttributes.alias, it's changing the alias to the most recent one. After that, all references to the same object (which in this case is every item in the members array) present the new value in alias.
const obj = { alias: 'abc', role: 'role1' }
const arr = []
arr.push(obj)
obj.alias = 'new alias'
arr.push(obj)
for (var mem of arr) {
console.log(mem)
}
To fix it, you need to create a new object each time and push it onto the array instead, like so:
aliasArr.forEach((currAlias) => {
// Creates a new object in memory with the same values, but overwrites alias
const newMemberAttributes = Object.assign(memberAttributes, { alias: currAlias });
console.log("memberAttributes:", newMemberAttributes);
members.push(newMemberAttributes);
console.log("members", members);
...
}
Similarly, you can use the spread operator to create a deep copy of the object and then reassign alias.
aliasArr.forEach((currAlias) => {
// Creates a new object in memory with the same values, but overwrites alias
const newMemberAttributes = { ...memberAttributes };
newMemberAttributes.alias = currAlias
console.log("memberAttributes:", newMemberAttributes);
members.push(newMemberAttributes);
console.log("members", members);
...
}

Why can't I delete a mongoose model's object properties?

When a user registers with my API they are returned a user object. Before returning the object I remove the hashed password and salt properties. I have to use
user.salt = undefined;
user.pass = undefined;
Because when I try
delete user.salt;
delete user.pass;
the object properties still exist and are returned.
Why is that?
To use delete you would need to convert the model document into a plain JavaScript object by calling toObject so that you can freely manipulate it:
user = user.toObject();
delete user.salt;
delete user.pass;
Non-configurable properties cannot be re-configured or deleted.
You should use strict mode so you get in-your-face errors instead of silent failures:
(function() {
"use strict";
var o = {};
Object.defineProperty(o, "key", {
value: "value",
configurable: false,
writable: true,
enumerable: true
});
delete o.key;
})()
// TypeError: Cannot delete property 'key' of #<Object>
Another solution aside from calling toObject is to access the _doc directly from the mongoose object and use ES6 spread operator to remove unwanted properties as such:
user = { ...user._doc, salt: undefined, pass: undefined }
Rather than converting to a JavaScript object with toObject(), it might be more ideal to instead choose which properties you want to exclude via the Query.prototype.select() function.
For example, if your User schema looked something like this:
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
},
name: {
type: String,
required: true
},
pass: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
module.exports = {
User: mongoose.model("user", userSchema)
};
Then if you wanted to exclude the pass and salt properties in a response containing an array of all users, you could do so by specifically choosing which properties to ignore by prepending a minus sign before the property name:
users.get("/", async (req, res) => {
try {
const result = await User
.find({})
.select("-pass -salt");
return res
.status(200)
.send(result);
}
catch (error) {
console.error(error);
}
});
Alternatively, if you have more properties to exclude than include, you can specifically choose which properties to add instead of which properties to remove:
const result = await User
.find({})
.select("email name");
The delete operation could be used on javascript objects only. Mongoose models are not javascript objects. So convert it into a javascript object and delete the property.
The code should look like this:
const modelJsObject = model.toObject();
delete modlelJsObject.property;
But that causes problems while saving the object. So what I did was just to set the property value to undefined.
model.property = undefined;
Old question, but I'm throwing my 2-cents into the fray....
You question has already been answered correctly by others, this is just a demo of how I worked around it.
I used Object.entries() + Array.reduce() to solve it. Here's my take:
// define dis-allowed keys and values
const disAllowedKeys = ['_id','__v','password'];
const disAllowedValues = [null, undefined, ''];
// our object, maybe a Mongoose model, or some API response
const someObject = {
_id: 132456789,
password: '$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/',
name: 'John Edward',
age: 29,
favoriteFood: null
};
// use reduce to create a new object with everything EXCEPT our dis-allowed keys and values!
const withOnlyGoodValues = Object.entries(someObject).reduce((ourNewObject, pair) => {
const key = pair[0];
const value = pair[1];
if (
disAllowedKeys.includes(key) === false &&
disAllowedValues.includes(value) === false
){
ourNewObject[key] = value;
}
return ourNewObject;
}, {});
// what we get back...
// {
// name: 'John Edward',
// age: 29
// }
// do something with the new object!
server.sendToClient(withOnlyGoodValues);
This can be cleaned up more once you understand how it works, especially with some fancy ES6 syntax. I intentionally tried to make it extra-readable, for the sake of the demo.
Read docs on how Object.entries() works: MDN - Object.entries()
Read docs on how Array.reduce() works: MDN - Array.reduce()
I use this little function just before i return the user object.
Of course i have to remember to add the new key i wish to remove but it works well for me
const protect = (o) => {
const removes = ['__v', '_id', 'salt', 'password', 'hash'];
m = o.toObject();
removes.forEach(element => {
try{
delete m[element]
}
catch(O_o){}
});
return m
}
and i use it as I said, just before i return the user.
return res.json({ success: true, user: await protect(user) });
Alternativly, it could be more dynamic when used this way:
const protect = (o, removes) => {
m = o.toObject();
removes.forEach(element => {
try{
delete m[element]
}
catch(O_o){}
});
return m
}
return res.json({ success: true, user: await protect(user, ['salt','hash']) });

Categories

Resources