How to use virtual on a nested field in mongoose - node.js

I am using mongoose in node.js. I have the following Schema.
const CustomerSchema = new mongoose.Schema({
...
email: {
type: String,
required: true,
lowercase: true,
trim: true
},
addresses: [
{
addressType: {
type: String,
enum: [ 'personal', 'shipping', 'billing' ]
},
street: {
type: String,
required: true,
trim: true
},
streetNumber: {
type: String,
trim: true
},
floor: {
type: String,
trim: true
},
apartament: {
type: String,
trim: true
},
cp: {
type: String,
required: true,
trim: true
},
district: {
type: String,
trim: true
},
city: {
type: mongoose.Schema.ObjectId,
ref: 'City',
required: true
}
}
]
});
I want to use "virtuals" to "add" a new field in every object in the array addresses?
How I can do this using virtuals? Is it possible?
I can achieve the same result using, but I would like to use virtuals.
const customerDB = await Customer.findById(idCustomer).lean()
customerDB.addresses = customerDB.addresses.map((address) => ({
...address,
addressDesc: mapTypeAddressDescription(address.addressType)
}));
Many Thanks!

As the name suggests virtuals are not added to the MongoDB documents. They are used for computed properties on documents.
Suppose you have a User model. Every user has an email, but you also want the email's domain. For example, the domain portion of 'test#gmail.com' is 'gmail.com'.
Below is one way to implement the domain property using a virtual. You define virtuals on a schema using the Schema#virtual() function.
const userSchema = mongoose.Schema({
email: String
});
// Create a virtual property `domain` that's computed from `email`.
userSchema.virtual('domain').get(function() {
return this.email.slice(this.email.indexOf('#') + 1);
});
const User = mongoose.model('User', userSchema);
let doc = await User.create({ email: 'test#gmail.com' });
// `domain` is now a property on User documents.
doc.domain; // 'gmail.com'
You should check the documentation for more details.
You can do something like this:
CustomerSchema.path('addresses').schema.virtual('fullAddr').get(function() {
return 'foo'
})
Also, it is useful to check this answer on stackoverflow if the above one doesn't work.

Related

Mongoose findBiId

I am working on a real estate project. The property's model is:
const propertySchema = new mongoose.Schema({
owner:{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
title: {
type: String,
require: true,
},
address: {
type: String,
require: true,
},
secondAddress: String,
city: {
type: String,
require: true,
},
state: {
type: String,
require: true,
},
zip: {
type: String,
require: true,
},
});
When I try to get the information of a specific property I get the error Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer. If I hardcode the id everything works. The route to get to the property is:
router.get('/property/:id', async(req, res) => {
const property = await Property.findById(req.params.id)
res.render('pages/user/property', { property })
})
How should I approach this?
You need _id in your schema in order to use findById, but you don't have one.
See documentation here,
and also this answer by JohnnyHK

How to update a nested array in mongoose?

I am new to the backend and trying to learn by building some stuff but unfortunately, I got stuck.
I want to know if I can update a nested array of objects in Users Schema using Mongoose in an efficient and elegant way.
Users Schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: {
type: String,
required: true
},
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
gender: {
type: String,
required: true
},
password: {
type: String,
required: true
},
friends: [{}],
notifications: []
}, {timestamps: true});
module.exports = User = mongoose.model('user', UserSchema);
In the friends' field, I stored friend request with the status of pending
I want if the user whose the request was sent to, hits an endpoint, to accept the request
by changing the status from pending to success.
This is how a friend request was stored:
friendRequest = {
_id: req.user.id,
status: 'pending',
sentByMe: false,
new: true,
inbox: []
}
Thanks as you help me out!!! 🙏🙏🙏
You should first create an additional friendRequest and inbox schemas like this:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
const InboxSchema = new Schema({
user_id: {
type: String,
required: true
},
from_id: {
type: String,
required: true
},
message: {
type: String,
required: true
},
the_date_time: {
type: Date,
required: true
}
});
mongoose.model('Inbox', InboxSchema);
const FriendRequestSchema = new Schema({
user_id: {
type: String,
required: true
},
status: {
type: String,
required: true
},
sentByMe: {
type: Boolean,
required: true,
unique: true
},
inbox: [InboxSchema]
})
mongoose.model('FriendRequests', FriendRequestSchema);
and update your Users schema:
const UserSchema = new Schema({
name: {
type: String,
required: true
},
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
gender: {
type: String,
required: true
},
password: {
type: String,
required: true
},
friends: [FriendSchema],
notifications: [FriendRequestSchema]
}, {timestamps: true});
And then use the friendRequest object
friendRequest = {
_id: req.user.id,
status: 'pending',
sentByMe: false,
new: true,
inbox: []
}
to update the Users collection
Users.update({ _id: user_id }, { $push: { notifications: friendRequest } });
Whenever you have arrays of objects within collections, its best to define additional schemas. You should also consider adding indexes to your collection schemas.
Update:
A FriendSchema would look like this:
const FriendsSchema = new Schema({
friend_id: {
type: String,
required: true
},
friend_name: {
type: String,
required: true
},
friendship_made: {
type: Date,
required: true
}
// you have to define FriendSchema before you define Users since you
// now reference [FriendSchema] in UserSchema
mongoose.model('Friends', FriendSchema);
And so is personA friends with personB?
Users.findOne({ "_id": personA.id, "friends.friend_id": personB.id});

Mongoose: Add validators on the fly on some parameters depending on queries

I am new to Mongoose and would like to know if it is possible to add validators on the fly on some parameters depending on queries. I have for example a schema like below:
var user = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
city: { type: String },
country: { type: String }
});
For a simple registration i force users giving the name, the email and the password. The Schema on top is OK. Now later I would like to force users giving the city and the country. Is it possible for example to update a user's document with the parameters city and country on required? I am avoiding to duplicate user schema like below:
var userUpdate = new Schema({
name: { type: String },
email: { type: String },
password: { type: String },
city: { type: String, required: true },
country: { type: String, required: true }
});
What you would need to do in this case is have one Schema and make your required a function which allows null and String:
var user = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
city: {
type: String,
required: function() {
return typeof this.city === 'undefined' || (this.city != null && typeof this.city != 'string')
}
}
});
You can extract this and make it an outside function which then you can use for county etc.
What this does is it makes the field required but also you can set null to it. In this way you can have it null in the beginning and then set it later on.
Here is the doc on required.
As far as I know, no, it is not possible.
Mongoose schema are set on collection, not on document.
you could have 2 mongoose model pointing to the same collection with different Schema, but it would effectively require to have duplicated Schema.
personnally, in your situation, I would create a single home-made schema like data structure and a function who, when feeded with the data structure, create the two version of the Schema.
by example :
const schemaStruct = {
base : {
name: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
city: { type: String },
country: { type: String }
}
addRequired : ["city", "country"]
}
function SchemaCreator(schemaStruct) {
const user = new Schema(schemaStruct.base)
const schemaCopy = Object.assign({}, schemaStruct.base)
schemaStruct.addRequired.forEach(key => {
schemaCopy[key].required = true;
})
const updateUser = new Schema(schemaCopy);
return [user, updateUser];
}

Find all the documents that has same subdocuments mongoose

I have postSchema which references the tagsSchema.
var tagsSchem = new Schema({
name: {
type: String,
required: true
}
}, {
timestamps: true
});
// create a schema
var postsSchema = new Schema({
title: {
type: String,
required: true,
unique: true
},
mainImage: {
type: String
},
category: {
type: String,
required: true
},
body: {
type: String,
required: true
},
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
tags: [tagsSchem]
}, {
timestamps: true
});
One post can contain any no. of tags. So if a post has 3 tags then I want to get all the posts with those 3 tags without querying it multiple times. Is it possible?
When you perform find, you can use the $in option to find values that are in your array. For example:
posts.find({tags:{$in:{["tag1","tag2","tag3"]}}, function(err,data) {
... //Your code here
}
This will take all the posts that contains one of the three tags. It's important you have to pass an array in the $in option. This should work.

Mongoose query through the element inside the array

I have a mongoose model:
let schema = new Schema({
email: {
type: String,
required: true,
unique: true
},
password: {
type: String
},
username: {
type: String,
unique: true
},
confirmed: {
type: Boolean
},
payload: [{
type: {
token: blablabla,
type: blablabla
}
}]
});
And i want find user by payload.token. How can I do that? I tried $elemMatch, but it does not work.
You can do
.find({'payload.type.token': token})
If payload is an array of objects and you want to find users by token value , below query should work :
db.users.find({payload: {$elemMatch: {'type.token':'blablabla'}}});

Resources