Default value for all queries via mongoose - node.js

I'm trying to write a mongoose schema that for all call to find() or findOne() will pass a certain value in one of its fields. I tried using the 'default' property on the field declaration, but that didn't work for me.
Here's my schema:
var schema = Schema({
created_at: Date,
type: {type: String, default: "alert"},
timestamp: Number,
order: Number,
description: String,
status: String,
});
I would like every call to find() and findOne() to pass the value "alert" in the "type" field.
Any ideas?

You could add a simple wrapper method to your model which will be responsible for finding every document with type: "alert". Something like this:
var Model = mongoose.model('Model', theSchema);
Model.alerts = function (q, callback) {
q.type = "alert";
this.find(q, callback);
}
Then you could get what you want with Model.alerts({}, callback).

Related

Cannot Query sub sub documents

I can't access sub sub documents I want to query the sub documents implement back end pagination so that I can render them in a separate page
I've tried using dot notation something.something.something and even using this ["something"]["something"]["something"] arrays since I recently found out objects are kind of arrays
This is the model Schema || the child Schema
const modelSchema = new mongoose.Schema({
modelname: {
type: String,
required: true,
minlength: 5,
maxlength: 250
}
});
This is the Bikes Schema || parent Schema
const bikeSchema = new mongoose.Schema({
make: {
type: makeSchema,
required: true
}
})
This is how data is stored in Mongodb
"_id" : ObjectId("5d5e13e8edcbbf038c1f9b8e"),
"make" : {
"_id" : ObjectId("5d40f0b40268d80ac8c30973"),
"makename" : "{ _id: 5d40f0b40268d80ac8c30973, makename: 'ducatii', __v: 0 }"
}
The expected output according to the documentation in order to query the makename ducatii is to do something like this
console.log(bikes[0]["make"]["makename"]["makename"])
or this
console.log(bikes[0].make.makename.makename)
To embedded document into a document you have to do this like the below
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
child: childSchema
});
Your case
const modelSchema = new mongoose.Schema({
makename: {
type: String,
required: true,
minlength: 5,
maxlength: 250
}
});
const bikeSchema = new mongoose.Schema({
make: {
makename: modelSchema
}
})
then you can access it like
console.log(bikes[0]["make"]["makename"]["makename"])
Two things to note:
If the returned result is a MongoDB cursor object, then you can't access via an array index. You'll need to retrieve the item from the cursor using the appropriate method.
Your first makename field points to a JSON string, not an object. You cannot access the nested makename.makename from a JSON string. You must first decode the JSON into an object before accessing the nested property.
This seems to be an issue with how you are storing your sub documents.
For example:
bikes[0].make.makename is actually a string, not a valid object. You won't be able to JSON.parse this string either, because you would have to wrap each key/value with escaped double quotes.
If you are able to store this sub-document correctly, then you should be good to use the object in the way you expect!

How can I make a mongo document filed read-only in mongoose schema field declaration? [duplicate]

Short and clear: is there any way to prevent setting a schema field but allowing to get the value?
I've been around the Mongoose Documentation but can't find what I'm looking for.
An alternative if you want to set a default value that can never be changed:
var schema = new Schema({
securedField: {
type: String,
default: 'Forever',
set: function (val) { return this.securedField; }
});
Define the field as a virtual getter instead of a traditional field.
For example, say you wanted to make the pop field of your collection read-only when accessed via Mongoose:
var schema = new Schema({
city: String,
state: String
});
schema.virtual('pop').get(function() {
return this._doc.pop;
});
By accessing the private _doc member of your model instance it's possible this may break in the future, but this worked fine when I tested it just now.
Since mongoose 5.6 you can do: immutable: true
var schema = new Schema({
securedField: {
type: String,
default: 'Forever',
immutable: true
}
});
You can just return from set the same value as the default value, no need to reference the _this document:
var schema = new Schema({
securedField: {
type: String,
default: 'Forever',
set: () => 'Forever'
});

mongoose: Insert a subdocument NOT an array

I am struggling to insert a document inside another document. I've looked at all the entries like this but they aren't quite what I am looking for.
Here is the scenario:
I have a common document that has its own schema. Lets call it a related record:
(function(){
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var relatedRecordSchema = new Schema({
params: {
recordId: Schema.Types.ObjectId,
recordType: String,
recordTitle: String
},
metadata: {
dateCreated: {type: Date, default: Date.now}
}
},{ _id : false });
mongoose.model('RelatedRecord', relatedRecordSchema);
})();
I have no trouble inserting this in an ARRAY inside document that require it. I.e its configured this way:
//Embedded
relationships: {
following: [mongoose.model('RelatedRecord').schema],
followers: [mongoose.model('RelatedRecord').schema],
blocked: [mongoose.model('RelatedRecord').schema]
}
This works perfectly.
The scenario that does not work is where there is a single related record, lets say the source of a notification:
var notificationSchema = new Schema({
params: {
title: String,
imageUrl: String,
source: mongoose.model('RelatedRecord').schema
},
metadata: {
dateCreated: { type: Date, default: Date.now },
dateViewed: Date
}
});
So when I am creating the notification I try and assign the previously prepared RelatedRecord
returnObj.params.source = relatedRecord;
The record appears during a debug to be inserted (it is inside a _docs branch but far deeper than I would expect) but when the object is saved (returnObj.save()) the save routine is abandoned without error, meaning it does not enter into the callback at all.
So it looks to me that i'm confusing mongoose as the dot assignment is forcing the subdoc into the wrong location.
So the question is simple:
How do I set that subdocument?
What the question isn't:
No I don't want to populate or advice on how you would solve this problem differently. We have sensible reasons for doing things how we are doing them.
Cheers
b
As Hiren S correctly pointed out:
1) Sub-Docs = array, always. Its in the first line in the docs :|
2) By setting the type to mixed, assignment of the object worked.
I'm a dumdum.

Storing a User Provided Date in Mongoose

So, I've got this schema:
var imageSchema = new Schema( {
caption: {type: String, required: true},
url: {type: String, required: true}
});
var EventSchema = new Schema({
name: {type: String, required: true},
date: {type: Date, required: true},
time: {type: String, required: true},
location: {type: String, required: true},
description: { type: String, required: false },
image: {type: String, required: false},
images: [imageSchema]
});
Requests are handled via locomotive.js, and the controller action for creating new records looks like this:
EventController.create = function() {
if(preScreen.screen.bind(this)("event", "create")) {
this.elements = modelHelper.loadValues.bind(this)();
this.saveMessage = "Save";
this.strings = strings;
if(this.req.method && this.req.method == "POST")
{
this._createEvent();
} else {
this.render();
}
} else {
this.redirect(this.urlFor({controller: "dashboard", action: "error"}));
}
};
This is a fairly standard action controller; mostly invoking an input view or or handling the _create when the received with a POST header.
the _createEvent function looks like this:
EventController._createEvent = function() {
if(!(this.elements)) this.elements = require('../templates/Event/elements')();
if(!(this.event)) this.event = new Event();
modelHelper.populate.bind(this)(this.elements, "event", function() {
modelHelper.save.bind(this)("event", this._confirm.bind(this), "create");
}.bind(this));
};
For my models I encapsulate all of the inputs in a template pattern. Rather than spend a lot of time on the framework around this (which I am working on releasing open source once I have finished doing small tweaks too) I will say that effectively the template contains one element for each path in the schema and provides some client-side details (error messages, labels etc.) these template objects are used by a modelHelper object which is fairly agnostic. Effectively what modelHelper.populate does is inspect the "type" property of each object within elements, and calls a handler for the appropriate input type.
the handler for date types is:
case "date" :
this[record][field.name] = strings.exists(this.param(field)) ?
strings.trim(this.param(field.name)) : null;
break;
although I've also tried strings.trim(Date.parse(this.param(field.name)) to get the UTC Timestamp from the user string.
I've been able to validate that the user entered date string does return a valid UTC stamp by using console.log within the date parser.
When the modelHelper.save() call is made it runs through these template objects, creates an associate array with the values picked up from the parsers and passes it to Record.save().
Most of this has been thoroughly tested and is being used in production however this is my first scenario where I am using dates other than date.now() as a default value.
What is the correct body for the date parser in order for mongodb/ the mongoose driver to push a date into a Date type?
Any string that JavaScript's Date.parse method can parse will work as the string is cast to a Date by Mongoose using this function which calls the Date constructor which uses Date.parse to parse strings.

Mongoose custom schema type date interval

I have many fields in my documents of type date intervals, such as this
{
publishDate:
{
start: {type: Date, required: true},
end: {type: Date, required: true}
}
}
To reduce duplication of the code and make it easier to maintain, how to create custom Mongoose type, for instance DateInterval, containing two fields:
start
end
and containing validator that makes sure both fields are filled out, and start is before end?
You can reuse schemas in mongoose.
var DateIntervalSchema = new Schema({
start: {type: Date, required: true},
end: {type: Date, required: true}
});
var SomeSchema = new Schema({
publishDate: [DateIntervalSchema],
// ... etc
});
You can also reference documents from other collections.
var SomeSchema = new Schema({
publishDate: {type: Schema.ObjectId, ref: 'DateInterval'}
});
//using populate
SomeModel.findOne({ someField: "value" })
.populate('publishDate') // <--
.exec(function (err, doc) {
if (err) ...
})
You'll want to develop a custom schema type. There are a number of plugins that do this already, one of which, for long numbers, can be found here: https://github.com/aheckmann/mongoose-long/blob/master/lib/index.js . This is a good basic example to follow.
For your purposes, then, you can create a DateInterval custom schema, casting it as type Date, and then use a validator to check start and end - http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate.
Since mongoose >=4.4 you can implement your custom schema type.
Documentation is not very clear, but you can follow this example.
You have to:
define your DateInterval custom object with toBSON() / toJSON() and toObject() prototype methods
define the DateIntervalType inherited from mongoose.SchemaType for handle the mongoose integration, and casting to DateInterval.
In this way you can achieve full control on memory (Mongoose model) and mongodb (raw's bson) data representation.

Resources