Recursive mongoose schema does not result in correct object stores - mongodb

I have a recursive model schema defined in a schema which uses the add() method in the Schema class to incrementally build the schema. It seems to build the paths correctly as shown when I print out the paths. However, when I use the Model defined to store the object in the database, it is missing the inner BNode. Here is a definition of the schema:
import mongoose from 'mongoose';
const BNodeSchema = new mongoose.Schema({
bValue: { type: [Number] },
id: String,
})
const RValue = {
rId: String,
value: Number
}
const ANodeSchema = new mongoose.Schema({
type: {
id: String,
rValues: {
type: Map,
of: RValue
},
}
})
const QuestSchema = new mongoose.Schema({
type: {
_id: { type: String, },
aNode: ANodeSchema,
bNodes: [BNodeSchema],
url: {
type: String
},
id: {
type: String
}
},
},
{ id: false }
)
ANodeSchema.add({ quest: QuestSchema });
const QuestNodeSchema = new mongoose.Schema({
_id: { type: String, unique: true },
quests: { type: [QuestSchema] },
}, {
id: false
})
export const QuestModel = mongoose.model('QuestModel', QuestNodeSchema);
QuestNodeSchema.eachPath(function(path:any) {
console.log(path);
});
{
_id: 12223,
quests:[
{
id: 'Quest-111-111' ,
aNode: {
id: 'A222222',
rValues: {
rId: 'RR1222',
value: 44422
},
quest:{
url: 'https://deptio-opcom',
id: '22222-QST',
bNodes:[{
bValue: 'B22190',
value: 22085
}]
}
}
}
]
}
I have included a sample of the json I am storing in the database. I use a class, not included for brevity to create the equivalent JSON object in the final format to be stored. My feeling is that there is something not quite right with my schema definition. I would be most grateful if someone could help me figure out what I am missing in my definition. Thanks a lot

Related

Why is `_id` not accepting by custom string using Mongoose?

I'm trying to make an _id field based off the title for topic object I've defined in my server. Here's the the schema.
const { gql } = require('apollo-server-express')
const typeDefs = gql`
type Topic #key(fields: "name") {
name: String,
desc: String,
body: String,
subject: [String]
}
`
And then here's the resolver
const resolvers = {
Mutation: {
addTopic(parent, args, context, info) {
const { name, desc, body, subject } = args
const topicObj = new Topic({
_id: name,
name,
desc,
body,
subject
})
return topicObj.save()
.then(result => {
return{ ...result._doc}
})
.catch(err => {
console.error(err)
})
}
}
}
The error that I'm getting is Cast to ObjectId failed for value "MyTopic" (type string) at path "_id".
Not too surprisingly, when I cast it manually with _id: mongoose.Types.ObjectId(name) I get the Argument passed in must be a single String of 12 bytes or a string of 24 hex characters error.
I must be misunderstanding, but this post lead me to believe my first approach is the right one so I'm not sure what to do to get it working.
I think I have to find some way to tell Mongoose not to try casting it but I'm not sure if that's what I should be doing.
Mongoose Model
const TopicSchema = new Schema({
name: {
type: String,
required: true
},
desc: {
type: String,
required: true
},
body: {
type: String,
required: true
},
subject: {
type: [String],
required: true
}
})
Because you haven't declared your _id on your Mongoose Schema, Mongoose is defaulting to an ObjectId type for your documents' _id instead of a String one that leads to the error.
To solve this you can declare the _id in your schema like this:
const TopicSchema = new Schema({
_id: String,
name: {
type: String,
required: true
},
desc: {
type: String,
required: true
},
body: {
type: String,
required: true
},
subject: {
type: [String],
required: true
}
})
You can read more here: How to set _id to db document in Mongoose?

Mongoose Find subdocument in array

I have the following data in Robo3t
With this model:
const eleccionSchema = new mongoose.Schema({
e: [{
id: {
type: String,
required: true
},
l:[...]
}],
eleccion: {
type: Number,
required: true,
ref: 'Corte'
}
})
//? Create the model
const Eleccion = mongoose.model('Eleccion', eleccionSchema)
Right now I'm trying to fetch some data based on e.id like this
const eleccion = await Eleccion.findOne({'e.id':'A'})
But it's actually returning the whole array instead of just one
Fixed it with a projection: https://docs.mongodb.com/manual/reference/operator/projection/elemMatch/
const eleccion = await Eleccion.findOne({}, {
'e':
{ $elemMatch: { id: 'A' } }
})

Mongoose Model containing arrays

First of all, I'm pretty new to MongoDB, Mongoose and Express. I'm trying to create a Mongoose model that has two arrays that I want to populate with multiple objects called itemSchema but I'm not sure how I'm supposed to update the array short of using findOneAndUpdate but since my array is initially empty there is no initial ID until a document is created. With the method that I have defined below - any already existing data in the food array is replaced by a new array. Below is my model -
const mongoose = require("mongoose");
const itemSchema = new mongoose.Schema({
id: String,
drinks: [
{
id: String,
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
],
food: [
{
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
]
});
module.exports = Item = mongoose.model("item", itemSchema);
I don't know if I'm defining the schema correctly. I know that it isn't very DRY ( since both arrays contain the same types ) but since I believe this is such a simple use case I don't want to define two separate schema for Drink and Food when I could just create one Schema.
router.post("/food", async (req, res) => {
try {
// Create an object from the request that includes the name, price and description
const newItem = {
name: req.body.name,
price: req.body.price,
description: req.body.description
};
// pass the object to the Items model
let item = new Items(newItem);
// add to the comments array
console.log("the new comment ", newItem);
item.food.unshift(newItem);
item.save();
// return the new item array to confirm adding the new item is working.
res.json(item);
} catch (error) {
// Display an error if there is one.
res.send(404).json(error);
}
});
The issue with the approach above comes from how I'm supposed to update the array. I defined the function below to update the food array for example but a new array gets created every single time. I believe that is has to do with not having Id param that I can use to provide the model with the findOneAndUpdate method. Any help would be greatly appreciated. Thank you in advance.
As per my opinion you can make your schema more simple as in your food and drinks array all the fields are same so you can simply take one more field as itemType and then you do not need to take two separate sub docs for food and drinks.
const mongoose = require("mongoose");
const itemSchema = new mongoose.Schema({
id: String,
itemType: { type: String }, // FOOD or DRINK
name: {
type: String,
required: true
},
price: {
type: String,
required: true
},
description: {
type: String
},
date: {
type: Date,
default: Date.now
}
});
If you wants to know more about updating in array with findOneAndUpdate() then i will explain two simple task to perform with this function.
CASE:1 If array of your sub doc is empty then you can push new document in your sub doc as below:
var updatedData = await Model.findOneAndUpdate(
{
_id: doc._id
},
{
$push: {
drinks: {
name: drink.name,
price: drink.price,
description: drink.description,
}
},
},{ new: true }
).lean().exec();
CASE:2 If you want to update existing sub doc by sub doc id then you can update as below:
var updatedData = await Model.findOneAndUpdate(
{
'drink._id': drinkId
},
{
$set: {
'drink.$.name': drink.name,
'drink.$.price': drink.price,
'drink.$.description': drink.description,
},
},{ new: true }
).lean().exec();

mongodb doesnt save 2 objects inside the main object

I want to save an object that has and object and array inside it. But when I end up saving the data in the mongo, it doesnt save a few properties.
like "entityMap": {}, data: {}
body =
{ entityMap: {},
blocks:
[ { key: '637gr',
text: 'Books selected for the you ',
type: 'header-four',
depth: 0,
inlineStyleRanges: [Array],
entityRanges: [],
data: {} } ] }
Heres how my mongo schema structured.
const mongoose = require('mongoose');
const { Schema } = mongoose;
const bookSchema = new Schema({
body: {
type: {},
required: false
},
templateName: {
type: String,
required: true
},
subject: {
type: String,
required: true
},
googleId: {
type: String,
required:true
},
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
});
mongoose.model('books', bookSchema);
When declaring the property with type {}, mongoose uses the Schema.Types.Mixed type. This way the property may contain anything, but mongoose won't detect changes made to it. You have to manually tell mongoose that the property was modified:
book.body = { foo: { bar: { quux: 'foo' } } }
book.markModified('body');
book.save()
Mongoose SchemaTypes

Push ObjectId to nested array in Mongoose

(Basic library CRUD application)
I am trying to create a document containing some global data about a given book, and then within a User document, add the ObjectId of the newly-created book to an array containing all books belonging to that user.
I have three data models in my application:
var userSchema = new mongoose.Schema({
name: String,
password: String,
email: String,
books: [BookInstanceSchema],
shelves: [String]
});
var bookSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
author: {
type: String,
required: true
},
description: String,
pageCount: Number,
ISBN: String,
googleID: String,
thumbnail: String,
publisher: String,
published: String,
});
var BookInstanceSchema = new mongoose.Schema({
bookId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Book'
},
userReview: String,
userRating: {
type: Number,
get: v => Math.round(v),
set: v => Math.round(v),
min: 0,
max: 4,
default: 0
},
shelf: String
});
The User model contains a nested array of BookInstances, which contain user-specific data such as ratings or reviews for a given book. A bookInstance in turn contains a reference to the global data for a book, to avoid duplicating data that isn't specific to any user.
What I'm trying to do is first save the global data for a book (thus generating an _id), and when done, save a bookInstance containing that _id in a given user's array of books:
router.post('/save/:id', function(req, res) {
var url = encodeurl('https://www.googleapis.com/books/v1/volumes/' + req.params.id);
request(url, function(err, response, data) {
parsedData = JSON.parse(data);
var newBook = {
title: parsedData.volumeInfo.title,
author: parsedData.volumeInfo.authors[0],
description: parsedData.volumeInfo.description,
pageCount: parsedData.volumeInfo.pageCount,
ISBN: parsedData.volumeInfo.description,
googleID: parsedData.id,
publisher: parsedData.volumeInfo.publisher,
published: parsedData.volumeInfo.publishedDate,
thumbnail: parsedData.volumeInfo.imageLinks.thumbnail
};
Book.create(newBook, function(err, newBook) {
if (err) {
console.log(err);
}
else {
console.log(newBook._id);
console.log(mongoose.Types.ObjectId.isValid(newbook._id));
User.findByIdAndUpdate(req.session.id, {
$push: {
"books": {
bookId: newBook._id,
userRating: 0,
userReview: ''
}
}
},
{
upsert: true
},
function(err, data){
if(err) {
console.log(err);
}
else {
res.redirect('/');
}
});
}
});
});
});
I'm getting the error:
message: 'Cast to ObjectId failed for value "hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0" at path "_id" for model "User"',
name: 'CastError',
stringValue: '"hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0"',
kind: 'ObjectId',
value: 'hjhHy8TcIQ6lOjHRJZ12LPU1B0AySrS0',
path: '_id',
reason: undefined,
Every time, the value in the error (in this case, jhHy8T...) is different than the newBook._id I'm attempting to push into the array:
console.log(newBook._id); // 5a120272d4201d4399e465f5
console.log(mongoose.Types.ObjectId.isValid(newBook._id)); // true
It seems to me something is wrong with my User update statement:
User.findByIdAndUpdate(req.session.id, {
$push: {
"books": {
bookId: newBook._id,
userRating: 0,
userReview: ''
}
}...
Any help or suggestions on how to better organize my data are appreciated. Thanks!