How to save child Schema in mongodb using mongoose - node.js

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.

Related

CastError: Cast to ObjectId failed for value "n" (type number) at path "propertyArray.$"

I know there are a lot of similar problems on stackoverflow.
Thing is, I can't solve it even following exactly mongodb documentation:
source
{
_id: 4,
grades: [
{ grade: 80, mean: 75, std: 8 },
{ grade: 85, mean: 90, std: 5 },
{ grade: 85, mean: 85, std: 8 }
]
}
db.students.updateOne(
{ _id: 4, "grades.grade": 85 },
{ $set: { "grades.$.std" : 6 } }
)
{
"_id" : 4,
"grades" : [
{ "grade" : 80, "mean" : 75, "std" : 8 },
{ "grade" : 85, "mean" : 90, "std" : 6 },
{ "grade" : 85, "mean" : 85, "std" : 8 }
]
}
Here's what I am trying with different combinations:
const { id, rateValue } = req.body
Book.updateOne({ _id: id, "userbookrates.user":ObjectId("62d1fbfa5a0a6281de43b528") }, {$set:{"userbookrates.$.rate": rateValue}})
.then(r => {
console.log(r, 'result log----')
})
.catch(e=> console.log(e))
My models:
BOOKS
const bookSchema = new Schema (
{
title: {
type:String,
required: [true, 'Mongoose - name is required']
},
author: {
type:String,
required: [true, 'Mongoose - author is required']
},
plot: {
type:String,
required: [true, 'Mongoose - plot is required']
},
genre: {
type:String,
required: [true, 'Mongoose - genre is required']
},
image: {
type: String,
default: 'catty'
},
owner: {
type: Schema.Types.ObjectId, ref: 'User'
},
userbookrates: [{
type: Schema.Types.ObjectId, ref: 'Bookrate'
}],
usersaved: [{
type: Schema.Types.ObjectId, ref: 'User'
}]
},
{
timestamps: true
}
);
module.exports = model('Book', bookSchema);
USER:
const userSchema = new Schema ({
email: {
type:String,
required: true,
required: [true, 'Mongoose - Email is required'],
unique: true,
match: [/^\w+([\.-]?\w+)*#\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please fill a valid email address']
},
password: {
type:String,
required: [true, 'Mongoose - Password is required'],
},
username: {
type: String
},
image: {
type: String,
default:'panda.png'
},
books: [{
type: Schema.Types.ObjectId, ref: 'Book'
}],
bookrate: [{
type: Schema.Types.ObjectId, ref: 'Bookrate'
}],
booksaved: [{
type: Schema.Types.ObjectId, ref: 'Book'
}],
followers: [{
type: Schema.Types.ObjectId, ref: 'User'
}],
following: [{
type: Schema.Types.ObjectId, ref: 'User'
}],
},
{
timestamps: true
}
);
BOOKRATE:
const bookrateSchema = new Schema ({
user: {
type: Schema.Types.ObjectId, ref: 'User'
},
rate: {
type: Number,
required: [true, 'Mongoose - Rate is a MUST'],
},
// book: {
// type: Schema.Types.ObjectId, ref: 'Book'
// }
},
{
timestamps: true
}
);
As you can see, I referenced for leave reviews in each book from user account.
I would like users to be able to leave reviews to their profile books, so if one user leaves one, and needs to change it, I want to update it from this book (unique ID), inside an array of raviews/ratings just the single value of rate
userbookrates: [{
type: Schema.Types.ObjectId, ref: 'Bookrate'
}],
Some example of book:
{
_id: new ObjectId("62d1fbd46dca32eab6e2f280"),
title: 'The Leisure Seeker',
author: 'Paolo Virzì',
plot: 'A runaway couple goes on an unforgettable journey in the faithful old RV they call The Leisure Seeker, traveling from Boston to The Ernest Hemingway Home in Key West. They recapture their passion for life and their love for each other on a road trip that provides revelation and surprise right up to the very end.',
genre: 'drama',
image: 'https://image.jpg',
userbookrates: [
{
_id: new ObjectId("62d218c7c93acc30d3075f79"),
user: new ObjectId("62d1fbfa5a0a6281de43b528"),
rate: 5,
createdAt: 2022-07-16T01:47:51.874Z,
updatedAt: 2022-07-16T01:47:51.874Z,
__v: 0
},
{
_id: new ObjectId("62d2209796fd7924d5ea7279"),
user: new ObjectId("62d1fbfa5a0a6281de43b528"),
rate: 5,
createdAt: 2022-07-16T02:21:11.566Z,
updatedAt: 2022-07-16T02:21:11.566Z,
__v: 0
}
],
usersaved: [],
createdAt: 2022-07-15T23:44:20.100Z,
updatedAt: 2022-07-16T02:21:11.581Z,
__v: 0
}
The ERROR:
**CastError: Cast to ObjectId failed for value "3" (type number) at path "userbookrates.$"**
at model.Query.exec (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/query.js:4650:21)
at model.Query.Query.then (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/query.js:4749:15)
at /home/titoih/Code/api-opinoos/routes/user/user.routes.js:29:10
at Layer.handle [as handle_request] (/home/titoih/Code/api-opinoos/node_modules/express/lib/router/layer.js:95:5)
at next (/home/titoih/Code/api-opinoos/node_modules/express/lib/router/route.js:137:13)
at module.exports.checkAuth (/home/titoih/Code/api-opinoos/mid/mid.secure.js:4:9)
at Layer.handle [as handle_request] (/home/titoih/Code/api-opinoos/node_modules/express/lib/router/layer.js:95:5)
at next (/home/titoih/Code/api-opinoos/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/home/titoih/Code/api-opinoos/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/home/titoih/Code/api-opinoos/node_modules/express/lib/router/layer.js:95:5) {
messageFormat: undefined,
stringValue: '"3"',
kind: 'ObjectId',
value: 3,
path: 'userbookrates.$',
**reason: BSONTypeError: Argument passed in must be a string of 12 bytes or a string of 24 hex characters**
at new BSONTypeError (/home/titoih/Code/api-opinoos/node_modules/bson/lib/error.js:41:28)
at new ObjectId (/home/titoih/Code/api-opinoos/node_modules/bson/lib/objectid.js:65:23)
at castObjectId (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/cast/objectid.js:24:12)
at ObjectId.cast (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/schema/objectid.js:247:12)
at ObjectId.SchemaType.applySetters (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/schematype.js:1179:12)
at ObjectId.SchemaType._castForQuery (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/schematype.js:1613:15)
at ObjectId.SchemaType.castForQuery (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/schematype.js:1603:15)
at ObjectId.SchemaType.castForQueryWrapper (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/schematype.js:1580:20)
at castUpdateVal (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/helpers/query/castUpdate.js:527:19)
at walkUpdatePath (/home/titoih/Code/api-opinoos/node_modules/mongoose/lib/helpers/query/castUpdate.js:344:24),
valueType: 'number'
}
Thank you.
Try to retrieve the Book first and then use the $in operator to search only Bookrates with the specific ids:
(I used await so make sure to declare your parent function async)
const { id, rateValue } = req.body;
const book = await Book.findById(id);
if (!book) return;
await Bookrate.update(
{
_id: { $in: book.userbookrates },
user: ObjectId('62d1fbfa5a0a6281de43b528'),
},
{ rate: rateValue }
);

How to get the total number of occurences of a specific item in every document's array element

I am implementing favoriting/unfavoriting functionality to my express app but I have a problem on how to count the the total number the post has been favorited.
Assuming I have this Schema for Recipe
RecipeSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
maxlength: 30
},
description: {
type: String,
default: ''
},
favoritesCount: {
type: Number,
default: 0
}
})
And Schema for User
const UserSchema = new mongoose.Schema({
username: {
type: String,
minlength: 8,
required: true,
unique: true
},
fullname: {
type: String,
maxlength: 40,
minlength: 4,
required: true
},
password: {
type: String,
required: true,
minlength: 8
}
favorites: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Recipe'
}]
}, { timestamps: true });
And now assuming I have this doc of Users,
How can I count the total number the Recipe ID (5daef9a2761d4b1668214dbc) present in each User doc's favorites array?
[{
username: 'john123',
email: 'john#test.com',
favorites: ['5daef9a2761d4b1668214dbc']
}, {
username: 'jane75',
email: 'jane#test.com',
favorites: []
}, {
username: 'johnwick',
email: 'johnwick#test.com',
favorites: ['5daef9a2761d4b1668214dbc']
}]
// Should yield 2
I looked up for answers but I can't find one. I'm new to mongodb and nodejs so please bear with me. Some answers that I saw are related to Aggregation.
So far I have tried this code. But it just return the number of User documents.
const User = require('./User') // the User model
RecipeSchema.methods.updateFavoriteCount = function() {
return User.count({
favorites: {
$in: [this._id]
}
}).then((count) => {
this.favoritesCount = count;
return this.save();
});
};
You can do it with the help of aggregation and with $size. For more detail, refer to this document.
Your query
db.collection.aggregate([
{
$project: {
username: 1,
email: 1,
totalFavritesCount: {
$cond: {
if: {
$isArray: "$favorites"
},
then: {
$size: "$favorites"
},
else: "NA"
}
}
}
}
])
Result
[
{
"_id": ObjectId("5a934e000102030405000000"),
"email": "john#test.com",
"totalFavritesCount": 1,
"username": "john123"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"email": "jane#test.com",
"totalFavritesCount": 0,
"username": "jane75"
},
{
"_id": ObjectId("5a934e000102030405000002"),
"email": "johnwick#test.com",
"totalFavritesCount": 1,
"username": "johnwick"
}
]
You can also check out the running code in this link.

geo element must be an array or object: type: "Point"

When I tried post request like this
"{ "imgUrl": "Server\Pictures\i14182109167", "text": "Myself in
seoul", "tag": ["seoul", "tour"], "geometry" : {"type": "Point","coordinates": [80,
-27]} }"
The error causes
'Can\'t extract geo keys: { _id:
ObjectId(\'5b8e204455526366f86a6383\'), tag: [ "seoul", "tour" ],
date: new Date(1536041028423), imgUrl:
"Server\Pictures\i14182109167", text: "Myself in seoul", geometry: {
type: "Point", coordinates: [ 80, -27 ], _id:
ObjectId(\'5b8e204455526366f86a6384\') }, __v: 0 } geo element must
be an array or object: type: "Point"' }
even I added "type": "Point" in post request but, why?
const geoSchema = new Schema({
type: {
type: String,
default: 'Point',
index: '2dsphere'
},
coordinates: {
type: [Number]
}
});
const memoSchema = new Schema({
imgUrl: {
type: String
},
text: {
type: String
},
date: {
type: Date,
default: Date.now
},
tag: {
type: [String]
},
user: {
type: Schema.Types.ObjectId,
ref: 'Memo'
},
geometry: geoSchema
})
I experienced this error, tried several schema declarations, until I fixed by this implementing this design:
1. Create a new schema, which has Point as a property.
const mongoose = require('mongoose');
const {Point} = require('mongoose-geojson-schema');
const pointSchema = new mongoose.Schema({
type: {
type: String,
enum: ['Point'],
required: true
},
coordinates: {
type: [Number],
required: true
}
});
2. Schema for the object including above as a property:
const itemsSchema = new mongoose.Schema({
description: {
type: String,
required: true
},
location: {
type: pointSchema,
required: true
}
)
const Item = mongoose.model('Item', ItemsSchema);
module.exports = Item;
enter code here
3. Finally, my controller succesfully instantiates the object like this:
var geolocation =
{
type: 'Point',
coordinates: [
lng,
lat
]
};
const item = new Item({
description: req.body.description,
location: geolocation
})
Hope that helps.
I changed geometry to loc it worked!
but, I don't know why...
I was facing the same issue even after doing everything mention here. After spending too much time on this problem I get to know that if we save a default value it will work correctly.
Just follow the above answer and add
location: {
type: pointSchema,
default: {
type: 'Point',
coordinates: [0, 0],
},
required: false,
},
And delete the previous documents if any document has location.

How to set limit for array size in Mongoose schema

Would you be kind to tell me is there any way to set limitation on array size while creating Mongoose schema. For example
var peopleSchema = new Schema({
name: {
type: String,
required: true,
default: true
},
/* here I want to have limit: no more than 10 friends.
Is it possible to define in schema?*/
friends: [{
type: Schema.Types.ObjectId,
ref: 'peopleModel'
}]
})
With a small tweak to your schema setup you can add a validate option:
var peopleSchema = new Schema({
name: {
type: String,
required: true,
default: true
},
friends: {
type: [{
type: Schema.Types.ObjectId,
ref: 'peopleModel'
}],
validate: [arrayLimit, '{PATH} exceeds the limit of 10']
}
});
function arrayLimit(val) {
return val.length <= 10;
}
starting from mongo 3.6 you can add validation for a collection at server end, each document inserted/updated will be validated against the validator $jsonSchema, only the valid gets inserted, validation error will be for invalid documents
db.createCollection("people", {
validator: {
$jsonSchema: {
bsonType: "object",
required: [ "name" ],
properties: {
name: {
bsonType: ["string"],
description: "must be a string"
},
friends: {
bsonType: ["array"],
items : { bsonType: ["string"] },
minItems: 0,
maxItems: 10,
description: "must be a array of string and max is 10"
}
}
}
}
});
collection
> db.people.find()
valid document
> db.people.insert({name: 'abc' , friends : ['1','2','3','4','5','6','7','8','9','10']})
WriteResult({ "nInserted" : 1 })
invalid document
> db.people.insert({name: 'def' , friends : ['1','2','3','4','5','6','7','8','9','10', '11']})
WriteResult({
"nInserted" : 0,
"writeError" : {
"code" : 121,
"errmsg" : "Document failed validation"
}
})
find
> db.people.find()
{ "_id" : ObjectId("5a9779b60546616d5377ec1c"), "name" : "abc", "friends" : [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" ] }
>
You can use $slice modifier at the time you push new friend id into your array
https://docs.mongodb.com/manual/reference/operator/update/slice/#up._S_slice
$push: {
friends: {
$each: [id],
$slice: -10
}
}
This is my schema and array limit on assignedToId via an external function.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const taskSchema = new Schema({
parentTask: {
trim: true,
type: Schema.Types.ObjectId,
ref: "task",
},
assignedToId: [{
trim: true,
type: Schema.Types.ObjectId,
ref: "Employees",
}],
createdBy: {
trim: true,
type: Schema.Types.ObjectId,
ref: "Employees",
required: [true, "User ID is required"]
},
createdByName: {
trim: true,
type: String,
required: [true, "Creater name is required"]
},
},
{
timestamps: true
});
// Validations for assignedTo employees' size
taskSchema.path('assignedToId').validate(function (value) {
console.log(value.length)
if (value.length > 10) {
throw new Error("Assigned person's size can't be greater than 10!");
}
});
const Tasks = mongoose.model("Tasks", taskSchema);
module.exports = Tasks;

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