Allowing fields to be nulled in MongoDB schemas - node.js

I currently have an DB that has optional parameter fields in the schema JSON objects. At the moment, when the fields are UPDATED from an existing value(whether that be string, date, or otherwise) to null, upon retrieving the object the field is not actually nulled. How do I modify these schema fields to allow certain fields (say, the last three) in the object to be nulled upon an update, and displayed as such upon a subsequent GET request? I've tried making the type an array and adding null, but that clearly is not correct. Ive also tried default: null but that also did not work.
data: {
// Mandatory params
name: { type: String, required: true, index: true },
id: { type: String, required: true },
type: { type: String, required: true },
// Optional params
parent: { type: db.objectId(), ref: 'organisation' }
parentSpid: { type: String },
memberStatus: { type: String },
memberTier: { type: String },
memberExpiry: { type: Date }
}

I assume you are using Mongoose to wrap mongo? Under the hood, your queries will be using the $set operator which explicitly does not overwrite fields without a new value.
You should use the $unset operator to nullify fields.
For example, if you wished to nullify parent you would do:
Model.findOneAndUpdate({ ...QUERY }, { $unset: { parent: 1 } });
The $unset object can contain any fields you wish to nullify. Be wary that validators don't always run on updates in Mongoose. See https://mongoosejs.com/docs/validation.html

Related

Filter documents using multiple fields

I have a collection named Project. Below is the schema description for Project collection :
const projectSchema = new mongoose.Schema(
{
projectName: {
type: String,
trim: true
},
licenceType: {
type: String,
trim: true,
lowercase: true
},
location: {
type: String,
lowercase: true,
trim: true
},
description: {
type: String,
trim: true
},
projectType: {
type: String,
default: 'public'
},
status: {
type: String,
default: 'open'
},
budget: {
type: Number
},
duration: {
type: Number
},
},
{
timestamps: true
}
);
I want to fetch documents using these fields as filters. The fields are :
licenceType
location
date (timestamp is made true in schema for this purpose)
I can use these three fields in any combination to fetch documents. There is a possibility that no filter is applied at all in which case its a simple fetching of all the documents in the collection.
I know I can dynamically build query using if--else if--else but I wanted to know is there any other more efficient way of handling such queries rather than using if--else blocks. If there would have been five or more fields for filtering purpose, there would be so many combinations to check using if--else block.
Appreciate any kind of help!!Thank You.
So, I assume there's some external trigger which actually modifies the filter matrix (eg. a request from some UI). Mongoose allows you to specify filter up-front in a form of an object, take a look below:
const query = Project.find({
licenceType: 'sometype',
location: 'somelocation'
});
Clearly you can see this...
{
licenceType: 'sometype',
location: 'somelocation'
}
...is an object. So, I think you could re-build the filtering object each time filters change (create an empty object let myFilters = {} and extend it with your filters: myFilters['licenceType'] = 'sometype') and pass myFilters to find function.

How to update an array inside of a mongoose schema?

How to update an array inside a mongoose schema with updateOne?
I have one model on my Node.Js application that I've made with mongoose schema.
One of the fields of my schema is an array:
guestsNames: []
I'm already able to save items inside of this array but I didn't find a way to update the items inside of it.
Here is my whole schema:
const screenImageSchema = mongoose.Schema({
company: {
type: String,
require: true,
trim: true
},
guestsNames: [],
imageName: {
type: String,
require: true
},
defaultImageName: {
type: String,
require: true
},
date: {
type: String,
default: Date.now,
require: true
},
activated: {
type: String,
default: 'Enabled'
},
wsType: {
type: String,
default: 'Image'
}
}, {timestamps: true});
...and my updateOne method:
screenImageSchema.methods.updateOne = function(id, screenImage) {
const updatedScreenImage = {
company: screenImage.company,
guestsNames: screenImage.guests,
imageName: screenImage.imageName,
defaultImageName: screenImage.defaultImageName,
date: screenImage.date,
activated: screenImage.activated,
wsType: screenImage.wsType
}
ScreenImage.updateOne(id, updatedScreenImage, {new: true});
}
The 'screenImage' parameter passed to the function is an object with all information that I need, including an array with all strings for guestsNames (I've already checked if the parameters are being passed correctly to the object and they are). All fields are being updated with this piece of code except the guestsNames field. What am I doing wrong and how can I make the guestsNames array be updated correctly?
Cheers.
You can update directly your array like this
ScreenImage.updateOne(id, { $set : { guestNames : newArray }})
You need to use $set to replace the value of a field, see this mongoDB $set
try this if it works
screenImageSchema.methods.updateOne = function(id, screenImage) {
const updatedScreenImage = {
company: screenImage.company,
guestsNames[0]: screenImage.guests,
imageName: screenImage.imageName,
defaultImageName: screenImage.defaultImageName,
date: screenImage.date,
activated: screenImage.activated,
wsType: screenImage.wsType
}
ScreenImage.updateOne(id, updatedScreenImage, {new: true});
}

Insert a document with mongoose without initialize the model with empty attributes

I want to insert a document in my database from a website form. I have a model created with mongoose and I want to save in the database only the attributes that contains data and I don't want to save empty attributes.
This is my model:
const localizationSchema = new Schema({
name: { type: String, required: true },
spins: [{ type: String }],
spinsForChild: [{ type: String }],
parent: { id: String, name: String },
localizationType: { type: String },
count: { type: Number, default: 0 },
countries: [{ id: String, name: String, cities: [{ id: String, name: String }] }]
});
const Localization = mongoose.model('Localization', localizationSchema);
When I try to save a new document, it creates in the database all attributes although I don't send it on my query.
Localization.create({
name: body.name,
localizationType: body.localizationType,
"parent.id": parent.id,
"parent.name": parent.name,
spins: spins,
spinsForChild: spinsForChild
}, function(err) {
if (err) return handleError(err);
res.redirect('/localizations');
});
This code, for example, inserts in the DB an empty array called "countries".
I tried to use strict: false in the model declaration but it didn't works.
You could use this answer.
But, thing you try to implement seems to be anti-pattern and can cause errors when you will try to use array update operators with undefined array. So, use it carefully.
Good luck!

Mongoose $push - no errors when: inserting object with existing unique field + inserting trash

What I'm trying to accomplish is to have an Array of Objects (the subdocument way: defined in a Schema) inside a main document, and I want this subdocument to behave as a document itself.
This is, when pushing an Object into the Subdocument Array, I would like that it threw an error:
if any of the unique fields of the object being inserted are already taken
if the object being inserted doesn't match the TemplateSchema
This would be my main document:
var ApplicationSchema = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
description: {
type: String
},
...
templates: {
type: [TemplateSchema]
}
});
and the following would be the subdocument, the templates field inside the ApplicationSchema:
var TemplateSchema = mongoose.Schema({
name: {
type: String,
required: true,
unique: true,
sparse: true
},
description: {
type: String
},
...
});
In this case, when I am trying to:
add a new object with an already existing name, or
push any trash into my subdocument
it's happening that:
no error is being thrown about the duplicated field value
the trash object (i.e. {some: "trash"}) is being inserted - not
really the object itself, but it's pushing an empty template
object inside the templates array
and I can't figure out why.
Here is the query I'm using to insert new template objects into the templates array inside the main document, which I guess is here where it is not working as expected:
exports.create = function(id, objTemplate, callback) {
ApplicationModel.findByIdAndUpdate(
{ _id: id },
{
$push: {
"templates": objTemplate
}
},
{
safe: true,
upsert: true,
new : true
},
function(err, application) {
// handle stuff
}
);
};

fieldname "type" in my schema

I have a really strange situtation; my schema has a fieldname which is called "type"
type: { type: String, default:"article" }
the schema is extended by subclasses which override the type field name to change the default value
when I create the document and directly after the cb fires, find it and output the result to console.log, I see "type" and the value "article" set as its value within the object.
when I access it with the mongo client, there is no field called "type". Getting just the type also doesn't show any document that have that field using this command
db.clouds.find({},{type:1}).pretty();
in a other script (that executes later), I get the document with mongoose again and there is also no "type" field there either.
Is "type" some special no-no word? My schema worked fine since ages and stopped working recently.
Thanks for your help
MongoDB: 2.4.9
Mongoose: 3.8.5
/edit, updated with real schema. My schema worked for a year until recently.
var Cloud = new Schema({
type :{ type:String, required:true },
children :[ { type:ObjectId } ], //Cloud ID
value :{ type: String, max:1024, default:function(){return "SomeRandomString" + Math.ceil(Math.random()*1000) } },
cats:[ {type: ObjectId} ]
},
{
collection:"clouds"
});
//make sure Mongooose provides the fields correctly;
Cloud.set('toJSON', { getters: true, virtuals: false } );
and subclassing like this (in another file, including the above file, thus making a new schema defininition)
var ArticleCloud=Cloud;
ArticleCloud.add(
{
type: { type: String, default:"article" },
url :{ type: String , index: true },
state: {type: Number, default: config.err[500].code, index: true },
weight:{type: Number, index:true}
}
);

Resources