How to use properly nested mongoose model and schema - node.js

This is my schema. I am new to mongoose but trying the policy why to send extra info when not required. I have tried to do a subDocument for comments and likes.
var post = new Schema({
postid: {type: Number, required: true, unique: true},
title: {type: String, required: [true, 'Title cannot be blank']},
startdate: {type: Date, required: true, default: Date.now},
enddate: {type: Date, required: true, default: new Date(+new Date() + 15 * 24 * 60 * 60 * 1000)},
comments: [
{
id: {type: Number, required: true},
message: {type: String, required: true},
userid: {type: String, required: true, unique: true},
updated_at: {type: Date, required: true, default: Date.now, select: false},
likes: [
{
userid: {type: String, required: true, unique: true},
updated_at: {type: Date, required: true, default: Date.now},
}
],
}
],
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
});
post.index({postid: 1});
I am doing some dirty tricks to get the data in rest api by using lean().
// post [GET]
[
{ postid: 1, title: "dfdsfadsf", startdate: "dafdsfadsf", enddate: "dsafdsfads", commentscount: 6},
{ postid: 2, title: "ffdsfadsf", startdate: "dafdsfadsf", enddate: "dsafdsfads", commentscount: 5},
]
// post/:id [GET]
{
postid: 1,
title: "dfdsfadsf",
startdate: "dafdsfadsf",
enddate: "dsafdsfads",
comments: [{
{id: 1, message: "ddsfsfadsfa", likescount: 6},
{id: 2, message: "dsfafdrsdsfa", likescount: 3},
{id: 3, message: "dsfaefdsdsfa", likescount: 4},
{id: 4, message: "dfsfdsfadsfa", likescount: 5},
{id: 5, message: "fdsfdsfadsfa", likescount: 7},
{id: 6, message: "dsfadwsfadsf", likescount: 0}
}]
}
// post/:id/comments/:commentid/likes [GET]
{
id: "1",
message: "fadsfads",
likes: [
{ userid: 1, updated_at: "some date" },
{ userid: 2, updated_at: "some date" },
{ userid: 3, updated_at: "some date" },
{ userid: 4, updated_at: "some date" },
{ userid: 5, updated_at: "some date" },
{ userid: 6, updated_at: "some date" }
]
}
Using mysql it was pretty easy to use an ORM and do all these with one single query. Now in mongoose I am doing this in a bad way, like
for the first route, I am doing
Posts.find({}).select({
postid: true,
title: true,
startdate: true,
enddate: true,
comments: true
}).lean().exec(function(err, doc){
if (doc) {
if(doc.comments.length > 0) {
doc.commentcount = doc.comments.length;
delete doc.comments;
}
}
});
Same way I am doing for other two routes. I feel there might be a proper way to do all these using mongoose model. I have tried using aggregate & populate. But not my piece of cake.
If anyone can guide how to use the ORM and fetch data properly for one, I'll be glad and can do the rest.

You can only add likescount: {type: Number} in comments field and increment this field when .push new object to likes field.

Related

mongoose multiple field select not working

With below schema i am trying to find with selected fields. In result I receive only first 2 org_name and description, but others not receiving. any idea. I am new to mongoose. I am not able to find any reason from documentation too. Any help will be appreciated.
My schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
let JobsSchema = new Schema ({
_id: {type:String, required: true, max:100},
sector: {type:String, required: true, max:100},
category: {type:String, required: true, max:100},
apply_mode: {type:String, required: true, max:100},
org_name: {type:String, required: true, max:100},
description: {type:String, required: true},
start_date: {type: Date},
end_date: {type: Date},
total_openings: {type: Number},
total_openings_description: {type:String},
min_salary: {type: Number},
max_salary: {type: Number},
salary_description: {type: String},
apply_steps: {type: String},
tags: [],
additional_dates: [{
additional_date_label: {type:String, required: true, max:100},
additional_date_value: {type:Date},
}],
age_limits: [{
age_limit_label: {type:String, required: true, max:100},
age_limit_min_value: {type: Number},
age_limit_max_value: {type: Number},
}],
application_fees: [{
application_fee_label: {type:String, required: true, max:100},
application_fee_value: {type: Number},
}],
url_links: [{
url_link_label: {type:String, required: true, max:100},
url_link_value: {type:String, required: true, max:100},
}]
},
{ collection: 'Jobs' }); // this line to match name in db
module.exports = mongoose.model('jobs', JobsSchema);
My query:
Job.find(
{},
"org_name description start_date end_date total_openings min_salary max_salary",
{ sort: { start_date: "asc" }, skip: pageNumber * pageSize, limit: pageSize },
(err, jobs) => {
if (err) {
res.render("error", {
errMsg: "issue in getting records. Try after sometime.",
errBody: err
});
}
if (jobs) {
res.render("listJob", {
jobs: jobs
});
}
}
);
Result
[
{
"_id": "Job1",
"org_name": "Test org",
"description": "<p>Test Desc</p><p><b>Hello world</b></p>"
},
{
"_id": "Job2",
"org_name": "Test org11",
"description": "<p>Test descr</p>"
},
{
"_id": "Job10",
"org_name": "Test inc 1",
"description": "<p>,smdfndfskj</p>"
}
]
I finally found that projection fields should be there in all documents then only it will apply.

Mongoose aggregate multiple collections with match and filtering

I've three mongoose models
When the user likes something it get saved in the Liked collection. But what I want to do is. Make a query that gives 10 available product the user hasn't liked before and is not his own product. I can't figure out how to make this possible in one query. Would it be possible?
User:
{
password: String,
type: {
type: String,
enum: ['user', 'admin'],
required: true,
default: "user"
},
firstName: {type: String},
middleName: {type: String},
lastName: {type: String},
gender: {type: String},
profilePicture: {type: Object},
setOwnProfilePicture: {type: Boolean, default: false},
facebook:{
id: {type: String},
token: {type: String}
}
}
Product:
{
condition: {
type: String,
enum: ['Als nieuw', 'Goed', 'Redelijk', 'Matig'],
required: true,
default: "Goed"
},
type: {
type: String,
enum: ['Ruilen', 'Doneren'],
required: true,
default: "Ruilen"
},
zipCode: {type: String},
houseNumber: {type: String},
distance: {type: Number},
likes: {type: Number, default: 0},
dislikes: {type: Number, default: 0},
title: {type: String},
description: {type: String},
owner: {type: Schema.ObjectId},
images: { type : Array , "default" : [] }
}
Liked:
{
ownerProductID: {type: Schema.ObjectId},
productID: {type: Schema.ObjectId},
liked: Boolean
}
UPDATE:
I find out what query to use, but in the result I have a field called liked but I want to filter this out, so I don't get any extra values in my result.
The query I use now:
db.products.aggregate(
// Pipeline
[
// Stage 1
{
$match: {'owner': {$ne: ObjectId("583f2f33ee975f4e8560b9fe")}}
},
// Stage 2
{
$lookup: {
"from" : "likes",
"localField" : "_id",
"foreignField" : "productID",
"as" : "liked"
}
},
// Stage 3
{
$match: {'liked.ownerProduct': {$nin: [ObjectId("585834609bb1aa1cbe98257b")]}}
},
// Stage 4
{
$limit: 10
},
]
);
Use aggregate to filter on multiple levels.
Assuming you have the USER_OBJECTID, first filter products on owner, then join the collection with Liked and filter again with user ID.
db.Product.aggregate([
{$match: {'owner': {$ne: '<USER_OBJECTID>'}}},
{$lookup: {
from: 'liked',
localField: '_id',
foreignField: 'productID',
as: 'likes'
}
},
{$match: {'likes.userID': {$nin: [<USER_OBJECTID>]}}},
{$limit: 10}
])
I managed to get the result I wanted. By using this query:
db.products.aggregate(
[
{
$match: {'owner': {$ne: ObjectId("583f2f33ee975f4e8560b9fe")}}
},
{
$lookup: {
"from" : "likes",
"localField" : "_id",
"foreignField" : "productID",
"as" : "liked"
}
},
{
$match: {'liked.ownerProduct': {$nin: [ObjectId("585834609bb1aa1cbe98257b")]}}
},
{
$limit: 10
},
{
$project: {
_id: 1,
owner: 1,
zipCode: 1,
title: 1,
description: 1,
houseNumber: 1,
images: 1,
dislikes: 1,
likes: 1,
type: 1,
condition: 1
}
},
]
);

Mongodb/Mongoose: one field will not updated by empty string

i am new in MongoDB/Mongoose and have the following problem:
my schema is nested with:
profile: {
school: {type: String, required: false},
subject: {type: String, required: false},
graduation: {type: String, required: false}
}
now i want to update school, subject and graduation with:
userRouter.put('/:id', function(req, res, next){
var updateObject = req.body;
User.findByIdAndUpdate(req.params['id'], updateObject,{upsert: true}, (err, success)=> {
if(err)
res.status(404).send(err);
res.status(200).send();
});
});
i know the put method replace the object, but the patch method doesn't run in my code. Now, when i sent:
{"school": "", "subject": "c", "graduation": ""}
subject and graduation will be overwritten, but school will not be empty - it contains the old stuff.
Do you have a solution? Thanks.
Tested like this school and graduation were modified...
let updateObject = {
profile: {school: "", subject: "c", graduation: ""}
};
user.findByIdAndUpdate(
req.params['id'],
updateObject,
{upsert: true},
function(err, success) {
if (err)
res.status(404).send(err);
res.status(200).send();
}
);
Used this testing schema:
new mongoose.Schema({
created: {type: Date, required: true, default: Date.now},
modified: {type: Date, required: true, default: Date.now},
profile: {
school: {type: String, required: false},
subject: {type: String, required: false},
graduation: {type: String, required: false}
}
},
{
collection: 'db_test',
strict: 'throw',
toJSON: {getters: true},
toObject: {getters: true}
}
);

How to save child Schema in mongodb using mongoose

Following is my order object that I am trying to save -
{
shipper:
{ firstName: 'Test ShipName',
address1: '10 Florida Ave',
phone1: '800-123-4567' },
consignee:
{ firstName: 'AAA Manufacturing',
address1: '100 Main Street' },
items:
[
{ length1: 45, weight1: 12, height1: 45, width1: 34 },
{ length2: 42, weight2: 34, height2: 90, width2: 54 }
]
}
On doing this -
Order(order).save(function(err, result){
if(err)
throw err;
console.log(result);
});
shipper, consignee are saving appropriate values but in database(mongodb), items are not saving properly -
"items" : [
{
"_id" : ObjectId("54e36e18c59700b513a5309d")
},
{
"_id" : ObjectId("54e36e18c59700b513a5309c")
}
],
Following is my oderSchema -
var orderSchema = mongoose.Schema ({
shipper: {type: addressSchema, 'Default':''}},
consignee: {type: addressSchema, 'Default':''} },
items: {type: [itemSchema], 'Default':''} },
});
Following is my itemSchema -
var itemSchema = mongoose.Schema({
length: {type: Number, required: false },
width: {type: Number, required: false },
height: {type: Number, required: false },
weight: {type: Number, required: false },
});
Let me know what I am doing wrong in saving the item info.
In your itemSchema, the properties are "length", "width" etc, however properties of the data that you're saving contains numbers at the end "length1", "length2", etc. You need to remove those numbers.

Mongoose query reference

I have the below schema structure for my application:
var User = new Schema({
name: {type: String, required: true},
screen_name: {type: String, required: true, index:{unique:true}},
email: {type: String, required: true, unique:true},
created_at: {type: Date, required: true, default: Date}
});
var Line = new Schema({
user: {type: Schema.ObjectId, ref: 'User'},
text: String,
entered_at: {type: Date, required: true, default: Date}
});
var Story = new Schema ({
sid: {type: String, unique: true, required: true},
maxlines: {type: Number, default: 10}, // Max number of lines per user
title: {type: String, default: 'Select here to set a title'},
lines: [Line],
created_at: {type: Date, required: true, default: Date}
});
var Story = db.model('Story', Story);
var User = db.model('User', User);
When i query a Story using the sid i want it to output the lines in that story but also the users screen_name.
Currently i'm using the below to query the story:
app.get('/api/1/story/view/:id', function(req, res){
Story.findOne({ sid: req.params.id }, function(err, story){
if (err) {
res.json(err);
}
else if(story == null) {
res.json(err);
}
else{
res.send(story);
}
});
});
Which just brings back the result like this:
{
"__v": 1,
"_id": "5117878e381d7fd004000002",
"sid": "DIY0Ls5NwR",
"created_at": "2013-02-10T11:42:06.000Z",
"lines": [
{
"user": "511782cab249ff5819000002",
"text": "TESTING",
"_id": "51178795381d7fd004000003",
"entered_at": "2013-02-10T11:42:13.000Z"
}
],
"title": "Select here to set a title",
"maxlines": 10
}
There is a user set-up with that _id but i'm not entirely sure how to output the story and for it to include the users screen_name or even the entire user information along with the output
{
"__v": 0,
"screen_name": "Tam",
"email": "test#test.com",
"name": "Tam",
"_id": "511782cab249ff5819000002",
"created_at": "2013-02-10T11:21:46.000Z"
}
I haven't tested it, but you should be able to use populate to get the full user doc for each lines element like this:
Story.findOne({sid: req.params.id})
.populate('lines.user')
.exec(function(err, story){ ...

Resources