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

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']) });

Related

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

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.

graphql passing dynamic data to mutation

haven't used graphql or mongodb previously. What is the proper way to pass objects for the update mutation?
Since the only other way i see to pass multiple dynamically appearing parameters is to use input type which is appears to be a bit ineffective to me (in terms of how it looks in the code, especially with bigger objects), i just pass the possible values themselves. however in this case i need to dynamically construct updateObject, which again, going to get messy for the bigger models.
for example now i did:
Mutation: {
updateHub: async (_, { id, url, ports, enabled }) => {
const query = {'_id': id};
const updateFields = {
...(url? {url: url} : null),
...(ports? {ports: ports} : null),
...(enabled? {enabled: enabled} : null)
};
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
any advise on the better way to handle this?
thanks!
It appears your code could benefit from using ES6 spread syntax -- it would permit you to deal with an arbitrary number of properties from your args object without the need for serial tertiary statements.
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = { ...restArgs };
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
If for some reason you need to explicitly set the undefined properties to null in your object, you could possibly use some a config obj and method like defaults from the lodash library as shown below:
import { defaults } from 'lodash';
const nullFill = { url: null, ports: null, enabled: null }; // include any other properties that may be needed
Mutation: {
updateHub: async (_, { id, ...restArgs } ) => {
const query = {'_id': id};
const updateFields = defaults(restArgs, nullFill);
const result = await HubStore.findByIdAndUpdate(query, updateFields);
return {
success: !result ? false : true,
message: 'updated',
hub: result
};
}
}
Also, FWIW, I would consider placing the dynamic arguments that could be potentially be updated on its own input type, such as HubInput in this case, as suggested in the graphql docs. Below I've shown how this might work with your mutation. Note that because nothing on HubInput is flagged as requird (!) you are able to pass a dynamic collection of properties to update. Also note that if you take this appraoch you will need to properly destructure your args object initially in your mutation, something like { id, input }.
input HubInput {
url: String
ports: // whatever this type is, like [String]
enabled: Boolean
// ...Anything else that might need updating
}
type UpdateHubPayload {
success: Boolean
message: String
hub: Hub // assumes you have defined a type Hub
}
updateHub(id: Int, input: HubInput!): UpdateHubPayload

Simplify javascript object creation using ES6 destructuring

Is there a way to simplify the update of the user object using destructuring where I have an old object and I want to update to the new object with the same names for the properties.
I want to use the same user object and update the values rather than creating a new object.
function UpdateUserProps(user, updatedUser) {
const { email, status } = updatedUser;
user.email = email;
user.status = status;
return user;
}
You could use spread syntax ... in object. This creates a new object.
function UpdateUserProps(user, updatedUser) {
return {...user, ...updatedUser}
}
You can also use parameter destructuring to take specific properties from update object.
function UpdateUserProps(user, {email, status}) {
return {...user, ...{email, status}}
}
let user = {
name: "foo",
email: "foo",
status: true
}
console.log(UpdateUserProps(user, {
email: "bar",
status: false
}))
You could do it without destructuring as well using Object.assign
function updateUserProps(user, updatedUser) {
return Object.assign({}, user, updatedUser);
}
If you want to update in the same user object
function updateUserProps(user, updatedUser) {
return Object.assign(user, updatedUser);
}

JS: Compare strings of an array with object fields

There is an object, which represents the role of an user:
const user = {
isEditor: false,
isAdmin: true,
isTranslator: false
}
I need to pass some elements to a function an check if any of this role has a true value in the user object.
result = hasPermission(user, ['editor', 'admin']) // true
result = hasPermission(user, ['editor', 'translator']) // false
I've some problems with that, as the roles are named a bit differently.
I thought also about using _.some() as I need to check for only one true value.
function hasPermission (user, roles) {
roles.forEach(role => {
user[role] // is not working as user keys named differently
})
}
What you are doing is not the proper way to think about it.
A users role are not individual properties, rather a collection of roles.
So your user object should be model like
const MyUser = {
roles: ["Admin","Editor"]
}
So then you can check if a user has a given role by working with a function like so :
function UserHasAnyRole(user,roleKeys)
{
if(user && user.roles && roleKeys && roleKeys.length)
{
return user.roles.filter(function(r){
return roleKeys.filter(function(k){ return k == r;}).length>0;
}).length > 0;
}
return false;
}
var neededRoles = ["Admin","Translator"];
if(UserHasAnyRole(MyUser,neededRoles))
{
// do stuff
}
This way of thinking about it scales much better, as individual properties is not a sustainable way in the long term
EDIT: to account for arrray of input roles. This is not tested tho, so there might be some syntax errors in there, but you get the idea...
Here is a solution the closest to your situation, without changing the shape of your objects and functions.
You can actually use some like this, it is quite simple:
const user = {
isEditor: false,
isAdmin: true,
isTranslator: false
};
function hasPermission (user, roles) {
return roles.some(role => user["is" + role[0].toUpperCase() + role.substring(1)]);
}
console.log(hasPermission(user, ['editor', 'admin']));
console.log(hasPermission(user, ['editor', 'translator']));
Hoping this will help you!
You can use the same property names, Object.entries() to get an array of property, values pairs of the object and Array.prototype.some() and Array.prototype.includes() to check if the values match
const user = {
isEditor: false,
isAdmin: true,
isTranslator: false
}
const hasPermission = (o, keys) =>
Object.entries(o).some(([key, prop]) => prop && keys.includes(key));
let result = hasPermission(user, ["isEditor", "isAdmin"]);
console.log(result);
result = hasPermission(user, ["isEditor", "isTranslator"]);
console.log(result);
Here is yet another solution.
const user = {
isEditor: false,
isAdmin: true,
isTranslator: false
};
function hasPermission (user, roles) {
const userRoles = Object
.entries(user)
.filter(([_, val]) => val)
.map(([key, _]) => key.replace('is', '').toLowerCase());
return roles.some(x => userRoles.includes(x));
}
console.log(hasPermission(user, ['editor', 'admin']));
console.log(hasPermission(user, ['editor', 'translator']));

Object: Deep omit

Is there a way to use _.omit on nested object properties?
I want this to happen:
schema = {
firstName: {
type: String
},
secret: {
type: String,
optional: true,
private: true
}
};
schema = _.nestedOmit(schema, 'private');
console.log(schema);
// Should Log
// {
// firstName: {
// type: String
// },
// secret: {
// type: String,
// optional: true
// }
// }
_.nestedOmit obviously doesn't exist and just _.omit doesn't affect nested properties, but it should be clear what I'm looking for.
It also doesn't have to be underscore, but in my experience it often just makes things shorter and clearer.
You could create a nestedOmit mixin that would traverse the object to remove the unwanted key. Something like
_.mixin({
nestedOmit: function(obj, iteratee, context) {
// basic _.omit on the current object
var r = _.omit(obj, iteratee, context);
//transform the children objects
_.each(r, function(val, key) {
if (typeof(val) === "object")
r[key] = _.nestedOmit(val, iteratee, context);
});
return r;
}
});
and a demo http://jsfiddle.net/nikoshr/fez3eyw8/1/
Detailed solution of this issue is posted in another thread. Please have a look at the below thread
Link - Cleaning Unwanted Fields From GraphQL Responses

Categories

Resources