When using find method, how can populate data from other collection. The join operation that we do with sql databases. Right now i am using something like :
code:
async find(data, params) {
let records = await super.find(data, params);
let newrecords = records.data.map(async (user) => {
let professordetails = await this.app
.service("users")
.get(user.professorId);
professordetails.password = undefined;
user.professorId = professordetails;
return user;
});
return await Promise.all(newrecords).then((completed) => {
return completed;
});
}
This is course service and its model :
module.exports = function (app) {
const modelName = "courses";
const mongooseClient = app.get("mongooseClient");
const { Schema } = mongooseClient;
const { ObjectId } = Schema;
const schema = new Schema(
{
name: { type: String, required: true },
details: { type: String, required: true },
professorId: { type: ObjectId, ref: "users", required: true },
enrolledStudents: [{ type: ObjectId, ref: "users" }],
},
{
timestamps: true,
}
);
// This is necessary to avoid model compilation errors in watch mode
// see https://mongoosejs.com/docs/api/connection.html#connection_Connection-deleteModel
if (mongooseClient.modelNames().includes(modelName)) {
mongooseClient.deleteModel(modelName);
}
return mongooseClient.model(modelName, schema);
};
This is something like a unwanted operation as we are having populate. But i couldn't do it with populate.
Related
I created an e-commerce website but want to change a couple of features of it, I want to allow users to create unlimited categories and subcategories. what is the best approach for it? I used Nodejs (express) and MongoDB (mongoose).
Currently, the DB schema is this one:
category.js
const mongoose = require('mongoose');
const { s, rs, n, ref } = require('../utils/mongo');
var schema = new mongoose.Schema(
{
user: ref('user'),
name: { ...rs, unique: true },
description: s,
image: s,
view: {
...n,
default: 0,
},
},
{ timestamps: true }
);
module.exports = mongoose.model('category', schema);
subcategory.js
const mongoose = require('mongoose');
const { s, rs, ref } = require('../utils/mongo');
var schema = new mongoose.Schema(
{
category: ref('category'),
name: { ...rs, unique: true },
description: s,
},
{ timestamps: true }
);
module.exports = mongoose.model('subcategory', schema);
Appreciate in advance
just use one model
import mongoose from "mongoose";
const CategorySchema = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
slug: {
type: String,
required: true,
unique: true
},
parent_id: {
type: mongoose.Schema.Types.ObjectId,
default: null
},
description: {
type: String,
default: null
},
image: {
url: String,
public_id: String
},
});
export mongoose.model('Category', CategorySchema);
// Create category
import { Category } from "./model/category.model"
const createCategory = async (data) => {
try {
return await new Category(data).save()
} catch (err) {
console.log(err)
}
}
// Get all category
const getAllCategory = async () => {
try {
const categories = await Category.find({});
if (!categories) return [];
return nestedCategories(categories);
} catch (err) {
console.log(err);
}
}
function nestedCategories(categories, parentId = null) {
const categoryList = [];
let category;
if (parentId == null) {
category = categories.filter(cat => cat.parent_id == null);
} else {
category = categories.filter(cat => String(cat.parent_id) == String(parentId));
}
for (let cate of category) {
categoryList.push({
_id: cate._id,
name: cate.name,
slug: cate.slug,
children: nestedCategories(categories, cate._id)
})
}
return categoryList;
}
If you want unlimited nested category you will only need one single category schema.
let schema = new mongoose.Schema(
{
user: ref('user'),
name: { ...rs, unique: true },
description: s,
image: s,
parent: { type: mongoose.Schema.Types.ObjectId }, //the parent category
view: {
...n,
default: 0,
},
},
{ timestamps: true }
);
module.exports = mongoose.model('category', schema);
Unfortunately, you cannot use populate and $lookup without specifying a specific level, so you need build the tree yourself if you need unlimited nested.
Node js
The method used to post the comment data
const { type, movieId, userId, review, timestamp } = req.body;
const movie = await Movie.findOneAndUpdate(
{ movieId: movieId, type: type },
{
$push: {
movieReview: { ... },
},
},
{ upsert: true, new: true },
(err, info) => {
...
}
);
Reactjs
The method used to submit the comment
const submit_comment = async () => {
....
const file = {
movieId: id,
type: type,
userId: userInfo._id,
comment: comment,
};
if (!commentFlag) {
const { data } = await axios.post("/api/movie/add-comment", file, config);
...
};
Mongoose Schema
const movieSchema = new mongoose.Schema({
movieId: String,
type: String,
...
comment: [{ type: mongoose.Schema.Types.ObjectId, ref: "Comment" }],
});
After I run my submit function it posts two objects with the same object _id in mongoDB
So I have a schema, which is like this:
const playerSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: 'user'
},
paid : {type: Boolean, default: false}
}
)
const tournamentSchema = new Schema(
{
name: {
type: String,
required: true
},
player:[ playerSchema])
so in the tournament model i get this as return:
{
"_id": "5fbf3afe1ecd92296c746db6",
"name": "1st Testing Tournament",
"player": [
{
"paid": false,
"_id": "5fbf3cfe6347784028da8181",
"user": "5fbf3b4c1ecd92296c746dcd"
}
]}
in the API I will have only the user ID and the tournament ID. I want to update paid in players array from false to true. Here is what I tried:
exports.put_confirmPayment= async(req, res)=>{
const uid = req.params.user_id
const tid = req.params.tid
const findData= {"_id": tid, "player.user": uid }
const changeData = {"player.$.paid": "true"}
try {
await Tournament.findOneAndUpdate( findData, {$set: {changeData}} )
const tDB = await Tournament.findById(tid)
res.status(200).json(tDB)
} catch (error) {
console.log(error)
res.status(500).json(error.message)
}
}
Where I am going wrong? and what should my approach be?
convert string id from string to object type using mongoose.Types.ObjectId
change "true" string to boolean true
return updated result using returnOriginal: false, or new: true both will return new updated result
have removed extra constant variables, don't create too much variables
exports.put_confirmPayment = async(req, res) => {
try {
const tDB = await Tournament.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId(req.params.tid),
"player.user": mongoose.Types.ObjectId(req.params.user_id)
},
{ $set: { "player.$.paid": true } },
{ returnOriginal: false }
);
res.status(200).json(tDB);
} catch (error) {
console.log(error);
res.status(500).json(error.message);
}
}
Playground
For more information refer mongoose findoneandupdate documentation.
I'm working on a project where in one model I need to set the value of a field based on another fields value. Let me explain with some code.
Destination model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const DestinationSchema = new Schema({
name: {
type: String,
required: true
},
priority: {
type: Number,
default: 0,
max: 10,
required: true
}
})
DestinationSchema.statics.getPriority = function(value) {
return this.findOne({ _id: value })
}
const Destination = mongoose.model('Destination', DestinationSchema)
exports.Destination = Destination
Task model
const mongoose = require('mongoose')
const { Destination } = require('../_models/destination.model')
const Schema = mongoose.Schema;
const TaskSchema = new Schema({
priority: {
type: Number,
required: true,
min: 0,
max: 25
},
from: {
type: Schema.Types.ObjectId,
ref: 'Destination',
required: true
},
to: {
type: Schema.Types.ObjectId,
ref: 'Destination',
required: true
},
type: {
type: Number,
required: true,
min: 0,
max: 3
}
}, {
timestamps: true
})
TaskSchema.pre('save', async function () {
this.priority = await Destination.getPriority(this.from).then(doc => {
return doc.priority
})
this.priority += await Destination.getPriority(this.to).then(doc => {
return doc.priority
})
this.priority += this.type
})
Task Controller update function
exports.update = async function (req, res) {
try {
await Task.findOneAndUpdate({
_id: req.task._id
}, { $set: req.body }, {
new: true,
context: 'query'
})
.then(task =>
sendSuccess(res, 201, 'Task updated.')({
task
}),
throwError(500, 'sequelize error')
)
} catch (e) {
sendError(res)(e)
}
}
When I create a new Task, the priority gets set in the pre save hook just fine as expected. But I'm hitting a wall when I need to change Task.from or Task.to to another destination, then I need to recalculate the tasks priority again. I could do it on the client side, but this would lead to a concern where one could just simply send a priority in an update query to the server.
My question here is, how can I calculate the priority of a Task when it gets updated with new values for from and to? Do I have to query for the document which is about to get updated to get a reference to it or is there another cleaner way to do it, since this would lead to one additional hit to the database, and I'm trying to avoid it as much as possible.
In your task schema.
you have to use pre("findOneAndUpdate") mongoose middleware. It allows you to modify the update query before it is executed
Try This code:
TaskSchema.pre('findOneAndUpdate', async function(next) {
if(this._update.from || this._update.to) {
if(this._update.from) {
this._update.priority = await Destination.getPriority(this._update.from).then(doc => {
return doc.priority
});
}
if(this._update.to) {
this._update.priority += await Destination.getPriority(this._update.to).then(doc => {
return doc.priority
});
}
}
next();
});
I've got the following mongoose models:
Place.js
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const placeSchema = new Schema({
title: { type: String, require: true },
filename: { type: String, require: true },
lociSets: [{ type: Schema.Types.ObjectId, ref: 'LociSet'}]
})
module.exports = mongoose.model("places", placeSchema)
LociSet.js
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const LociSchema = require('./Locus')
const lociSetSchema = new Schema({
title: { type: String, require: true },
creator: { type: Schema.Types.ObjectId, ref: 'User' },
public: { type: Boolean, default: true },
loci: [LociSchema]
})
module.exports = mongoose.model("lociSets", lociSetSchema)
Locus.js
const mongoose = require("mongoose")
const Schema = mongoose.Schema
const locusSchema = new Schema({
position: {
x: { type: Number, require: true },
y: { type: Number, require: true },
z: { type: Number, require: true }
}
})
module.exports = locusSchema
Problem:
I try to insert a new LociSet into the lociSet array of Place like so:
exports.createOne = async (req, res) => {
const {
title,
public = true,
loci = []
} = req.body
console.log(title,public,loci,req.user.id)
const lociSet = new LociSet({
title,
public,
loci,
creator: req.user.id
})
try {
const place = await Place.findOne({
"title": req.params.title.toLowerCase()
})
console.log(lociSet)
await lociSet.save()
await place.lociSets.push(lociSet)
await place.save()
} catch (err) {
res.status(500).send({
message: "Some error occurred while creating the loci set.", err
});
}
}
But then I get an error message saying "Cast to [undefined] failed for value \"[{\"title\":\"Test set\",\"creator\":\"5a7898c403999200c4ee3ae5\",\"public\":\"true\"}]\" at path \"lociSets\""
The LociSet model is created without problems, but it seems to break when I try to save the place model
Because lociSets is an array of ObjectId references, you may want to try the following approach:
exports.createOne = async (req, res) => {
const { title, public = true, loci = [] } = req.body
const lociSet = new LociSet({
title,
public,
loci,
creator: req.user.id
})
try {
const newLociSet = await lociSet.save()
const place = await Place.findOneAndUpdate(
{ "title": req.params.title.toLowerCase() },
{ "$push": { "lociSets" : newLociSet._id } },
{ "new": true}
)
res.status(200).json(place)
} catch (err) {
res.status(500).send({
message: "Some error occurred while creating the loci set.", err
})
}
}