Mongoose Schema not predefined - node.js

I would like to create a "free" schema. A schema where the customer can push his own key/value.
This is my Schema :
const balanceSchema = new mongoose.Schema({
userId: { type: String },
incomes: { type: String },
fees: { type: String }
})
I would like the customer to push in "incomes" ans "fees" categories any key: value he wants, and as much as he wants.
For example, this JSON file :
{
"userId": "9450654064560845",
"incomes": {
"label": 2000,
"label2": 1000,
"label3": 500
},
"fees": {
"fee1": 45,
"mySuperFee": 300
}
}
With the Schemas, I can't push something that is not in the Schema.
Any idea how ?

Related

How to updates nested object using mongoose?

i'm new to mongoose, I want to update my city_name, my schema structure looks like
const addressSchema = new mongoose.schema({
address:{
door_no:{type:number},
other_details:{
street_name:{ type: string},
city:{
city_name:{type:string},
pincode:{type:string},
}
}
}
})
sample db data :
{
"_id": "63dw8sdhs8ad0s",
"address": {
"door_no": 43,
"other_details": {
"street": "sdsadada",
"city": {
"city_name": "dfaef"
}
}
}
}
now i want to update the city_name i tried this query
addressModel.findByIdAndUpdate({_id:id},{$set:{'address.other_details.city.city_name':'44xd3xc'}})
but it doesn't update for me.
check that you passing id crctly or not
https://mongoplayground.net/p/ZDPiTrMUHha

Mongoose populate 3 deep nested schema with JSON response

I have a find() query that when executed, I can see the json with the nested schemas that I want to see except for the 'artista' attribute only displays the id, instead of the properties I want. See below:
{
"total": 1,
"ordenes": [
{
"artpieces": [
{
"_id": "60c1388f30316c02b9f6351f",
"artista": "60c055736c7ca511055a0e1a",
"nombre": "LILIES"
},
{
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"artista": "60c136bf30316c02b9f6351b"
}
],
"_id": "60c414f9ea108a14ef75a9fb",
"precio": 3000,
"usuario": {
"_id": "609c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
The query I use to get the json above:
const [total, ordenes] = await Promise.all([
Orden.countDocuments(),
Orden.find()
.populate("usuario", "nombre")
.populate("artpieces", ["nombre","artista","nombre"])
]);
res.json({
total,
ordenes
});
It's an order schema that has artpieces. Each artpiece (I called it 'producto'), has a name a genre, an author/artist and the user which the order belongs to.
my Schema for the orden.js:
const { Schema, model } = require('mongoose');
const OrdenSchema = Schema({
artpieces: [
{
type: Schema.Types.ObjectId,
required: true,
ref: 'Producto'
}
],
estado: {
type: Boolean,
default: true,
required: true
},
usuario: {
type: Schema.Types.ObjectId,
ref: 'Usuario',
required: true
},
precio: {
type: Number,
required: true
}
})
OrdenSchema.methods.toJSON = function () {
const { __v, estado, ...data} = this.toObject();
return data;
}
module.exports = model('Orden', OrdenSchema);
Last thing I want to mention, I know for a fact that I have the code necessary in the artista.js model to display the name of the artist because I have a similar query to display all the artpieces with each artpiece have a genre and an artist.
That example looks like so (to give context):
{
"total": 4,
"productos": [
{
"precio": 0,
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"categoria": {
"_id": "60c04e3605d3c10ed10389e4",
"nombre": "NEO CUBISMO"
},
"artista": {
"_id": "60c136bf30316c02b9f6351b",
"nombre": "PICASSO"
},
"usuario": {
"_id": "609c8c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
What am I doing wrong that I can't get my json result at the top look like the json at the bottom, where the artist attribute is?
Also just to point out, I have checked how to nest populate methods in order SO posts including the path and the ref to the Schema and still haven't been able to get the expected result.

Mongoose findById

I am trying desperately to find a object stored with mongodb, with nodejs and mongoose.
The model of the object looks like:
const SimpleResourceSchema = new mongoose.Schema(
{
_id: String,
title: String,
objective: String,
url: String,
content: String,
active: Boolean,
type: String,
owner: String,
},
{
timestamps: true,
// _id: false,
}
);
export const SimpleResourceModel = mongoose.model<
SimpleResource & mongoose.Document
>('simpleResource', SimpleResourceSchema);
The query is made with 'id' parameter value '5f1da9737917360dd038bfc0':
return await SimpleResourceModel.findById(id).exec();
The data stored in mongodb is:
{
"_id": {
"$oid": "5f1da9737917360dd038bfc0"
},
"title": "Learn cooking",
"objective": "<p>Is the fridge empty ?</p>",
"content": "...",
"url": "..",
"active": true,
"type": "simple",
"owner": "5efceb2f63b75c1750846b0a",
"createdAt": {
"$date": "2020-07-26T16:04:03.806Z"
},
"updatedAt": {
"$date": "2020-07-26T16:04:03.806Z"
},
"__v": 0
}
I have looked around to get a solution, but have not found any solution to this roadblock.
Anyone can help ?
The main issue that when you define the schema you defined the id as string remove _id: String from schema definition. and it automatic be added.
If you want to add _id to typescript you can create interface
export interface SimpleResource extends Document {
_id: schema.Types.ObjectId,
...
}
then in model you directly add it but _id already defined in Document interface
and make sure that you install #types/mongoose
export const SimpleResourceModel = mongoose.model<SimpleResource>('simpleResource', SimpleResourceSchema);
Have you tried?
var ObjectId = require('mongoose').Types.ObjectId;
return await SimpleResourceModel.findById(new ObjectId(id)).exec();
i still get a null response when I try:
await SimpleResourceModel.findById(mongoose.Types.ObjectId(id)).exec()

Mongodb update multiple documents with different values

I have been trying to use updatemany with mongoose. I want to update the values in database using an array of objects.
[
{
"variantId": "5e1760fbdfaf28038242d676",
"quantity": 5
},
{
"variantId": "5e17e67b73a34d53160c7252",
"quantity": 13
}
]
I want to use variantId as filter.
Model schema is:
let variantSchema = new mongoose.Schema({
variantName: String,
stocks: {
type: Number,
min: 0
},
regularPrice: {
type: Number,
required: true
},
salePrice: {
type: Number,
required: true
}
})
I want to filter the models using variantId and then decrease the stocks.
As you need to update multiple documents with multiple criteria then .updateMany() wouldn't work - it will work only if you need to update multiple documents with same value, Try this below query which will help you to get it done in one DB call :
const Mongoose = require("mongoose");
let variantSchema = new mongoose.Schema({
variantName: String,
stocks: {
type: Number,
min: 0
},
regularPrice: {
type: Number,
required: true
},
salePrice: {
type: Number,
required: true
}
})
const Variant = mongoose.model('variant', variantSchema, 'variant');
let input = [
{
"variantId": "5e1760fbdfaf28038242d676",
"quantity": 5
},
{
"variantId": "5e17e67b73a34d53160c7252",
"quantity": 13
}
]
let bulkArr = [];
for (const i of input) {
bulkArr.push({
updateOne: {
"filter": { "_id": Mongoose.Types.ObjectId(i.variantId) },
"update": { $inc: { "stocks": - i.quantity } }
}
})
}
Variant.bulkWrite(bulkArr)
Ref : MongoDB-bulkWrite
I don't think this can be done with a single Model.updateMany query. You will need to loop the array and use Model.update instead.
for (const { variantId, quantity } of objects) {
Model.update({ _id: variantId }, { $inc: { stocks: -quantity } });
}
To run this in a transaction (https://mongoosejs.com/docs/transactions.html), the code should look something like this (however I have not tried or tested this):
mongoose.startSession().then(async session => {
session.startTransaction();
for (const { variantId, quantity } of objects) {
await Model.update({ _id: variantId }, { $inc: { stocks: -quantity } }, { session });
}
await session.commitTransaction();
});

How to use different types of References and populate

So i have two schemas, Article and Event
Both have an image field.
For Article,
featured_image: {
type: String,
default: '',
}
For Event,
featured_image: {
type: Schema.ObjectId,
ref: 'Medium'
}
I have another schema, Card, like this
type: {
type: String,
enum: ['Article', 'Event']
},
data: {
type: Schema.ObjectId,
refPath: 'type'
}
I am trying to populate the cards, like this
Card
.find(query)
.populate({
path: 'data',
populate: [{
path: 'featured_image',
model: 'Medium',
select: 'source type'
}]
};)
However, it keeps giving me a cast error, because when card is of type Event, it populates fine, but when it's of type 'Article', featured_image field is of string type and hence cannot be populated.
How do i populate featured_image field only if card is of type Event or it's a reference id, instead of string.
Instead of what you are attempting to do you should be using "discriminators", which is in fact the correct way to handle a relationship where the object types vary in the reference given.
You use discriminators by the different way in which you define the model, which instead constructs from a "base model" and schema as in:
const contentSchema = new Schema({
name: String
});
const articleSchema = new Schema({
image: String,
});
const eventSchema = new Schema({
image: { type: Schema.Types.ObjectId, ref: 'Medium' }
});
const cardSchema = new Schema({
name: String,
data: { type: Schema.Types.ObjectId, ref: 'Content' }
});
const Medium = mongoose.model('Medium', mediumSchema);
const Card = mongoose.model('Card', cardSchema )
const Content = mongoose.model('Content', contentSchema);
const Article = Content.discriminator('Article', articleSchema);
const Event = Content.discriminator('Event', eventSchema);
So instead you define a "base model" such as Content here which you actually point the references to within Event.
The next part is that the differing schema are actually registered to this model via the .discriminator() method from the base model, as opposed to the .model() method. This registers the schema with the general Content model in such a way that when you refer to any model instance defined with .discriminator() that a special __t field is implied to exist in that data, using the registered model name.
Aside from enabling mongoose to .populate() on different types, this also has the advantage of being a "full schema" attached to the different types of items. So you have have different validation and other methods as well if you like. It is indeed "polymorphism" at work in a database context, with helpful schema objects attached.
Therefore we can demonstrate both the varied "joins" that are done, as well as that you can now both use the individual models for Article and Event which would deal with only those items in all queries and operations. And not only can you use "individually", but since the mechanism for this actually stores the data in the same collection, there is also a Content model which gives access to both these types. Which is in essence how the main relation works in the definition to the Event schema.
As a full listing
const async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.set('debug',true);
mongoose.Promise = global.Promise;
mongoose.connect('mongodb://localhost/cards');
const mediumSchema = new Schema({
title: String
});
const contentSchema = new Schema({
name: String
});
const articleSchema = new Schema({
image: String,
});
const eventSchema = new Schema({
image: { type: Schema.Types.ObjectId, ref: 'Medium' }
});
const cardSchema = new Schema({
name: String,
data: { type: Schema.Types.ObjectId, ref: 'Content' }
});
const Medium = mongoose.model('Medium', mediumSchema);
const Card = mongoose.model('Card', cardSchema )
const Content = mongoose.model('Content', contentSchema);
const Article = Content.discriminator('Article', articleSchema);
const Event = Content.discriminator('Event', eventSchema);
function log(data) {
console.log(JSON.stringify(data, undefined, 2))
}
async.series(
[
// Clean data
(callback) =>
async.each(mongoose.models,(model,callback) =>
model.remove({},callback),callback),
// Insert some data
(callback) =>
async.waterfall(
[
(callback) =>
Medium.create({ title: 'An Image' },callback),
(medium,callback) =>
Content.create(
[
{ name: "An Event", image: medium, __t: 'Event' },
{ name: "An Article", image: "A String", __t: 'Article' }
],
callback
),
(content,callback) =>
Card.create(
[
{ name: 'Card 1', data: content[0] },
{ name: 'Card 2', data: content[1] }
],
callback
)
],
callback
),
// Query and populate
(callback) =>
Card.find()
.populate({
path: 'data',
populate: [{
path: 'image'
}]
})
.exec((err,cards) => {
if (err) callback(err);
log(cards);
callback();
}),
// Query on the model for the discriminator
(callback) =>
Article.findOne({},(err,article) => {
if (err) callback(err);
log(article);
callback();
}),
// Query on the general Content model
(callback) =>
Content.find({},(err,contents) => {
if (err) callback(err);
log(contents);
callback();
}),
],
(err) => {
if (err) throw err;
mongoose.disconnect();
}
);
And the sample output for different queries
Mongoose: cards.find({}, { fields: {} })
Mongoose: contents.find({ _id: { '$in': [ ObjectId("595ef117175f6850dcf657d7"), ObjectId("595ef117175f6850dcf657d6") ] } }, { fields: {} })
Mongoose: media.find({ _id: { '$in': [ ObjectId("595ef117175f6850dcf657d5") ] } }, { fields: {} })
[
{
"_id": "595ef117175f6850dcf657d9",
"name": "Card 2",
"data": {
"_id": "595ef117175f6850dcf657d7",
"name": "An Article",
"image": "A String",
"__v": 0,
"__t": "Article"
},
"__v": 0
},
{
"_id": "595ef117175f6850dcf657d8",
"name": "Card 1",
"data": {
"_id": "595ef117175f6850dcf657d6",
"name": "An Event",
"image": {
"_id": "595ef117175f6850dcf657d5",
"title": "An Image",
"__v": 0
},
"__v": 0,
"__t": "Event"
},
"__v": 0
}
]
Mongoose: contents.findOne({ __t: 'Article' }, { fields: {} })
{
"_id": "595ef117175f6850dcf657d7",
"name": "An Article",
"image": "A String",
"__v": 0,
"__t": "Article"
}
Mongoose: contents.find({}, { fields: {} })
[
{
"_id": "595ef117175f6850dcf657d6",
"name": "An Event",
"image": "595ef117175f6850dcf657d5",
"__v": 0,
"__t": "Event"
},
{
"_id": "595ef117175f6850dcf657d7",
"name": "An Article",
"image": "A String",
"__v": 0,
"__t": "Article"
}
]

Resources