Mongoose custom schema type date interval - node.js

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.

Related

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.

Why Mongoose Populate Required?

Using mongoose populate:
http://mongoosejs.com/docs/populate.html
It seams that mongoose is forcing me to declare a ref value for populate when I first create the document but in my case i don't have the ref info yet. When I try to create a new document while providing an empty string I get to my developer field I get:
{"message":"Cast to ObjectId failed for value \"\" at path \"developer\"","name":"CastError","type":"ObjectId","value":"","path":"developer"}
Object that I'm saving through mongoose:
var Project = {
name: 'Coolproject',
status: 'pending',
developer: '',
type: 'basic',
};
Project.create(req.body, function(err, project) {
if(err) { return handleError(res, err); }
return
});
My Model:
var ProjectSchema = new Schema({
name: String,
status: {type:String, default:'pending'},
developer:{type: Schema.Types.ObjectId, ref: 'User'},
type:String
});
Basically I need to set it later, but it doesn't seam like this is possible. Currently my work around is populate it with a dummy user until later but this is less than desirable.
Thoughts?
Update
Realized that if i provide a object id like value (55132a418b3cde5546b01b37) it lets me save the document. Very odd. Guess it just figured it can find the document moves on. Wondering why this doesn't happen for a blank value.
The problem is explained in the error message. You cannot save an Empty String in the place of an ObjectId. The field is not listed as 'required', so there is no problem leaving it out and saving the document.
Code correction:
// you can save this
var Project = {
name: 'Coolproject',
status: 'pending',
type: 'basic',
};
You need to use the sparse index in model.
So, the valid model can have developer equal to nil
var ProjectSchema = new Schema({
name: String,
status: {type:String, default:'pending'},
developer:{type: Schema.Types.ObjectId, ref: 'User', sparse:true},
type:String
});
See
http://mongoosejs.com/docs/api.html#schematype_SchemaType-sparse and
http://docs.mongodb.org/manual/core/index-sparse/
for additional info

save mongoose model and submodel after populate

I'm trying to find a way to simplify my backend logic and optimize my DB, for this it would be great if I could save a document and it's subdocuments all at once so that creating subdocuments becomes easier.
Here is a simple example:
the primary model:
var ItemSchema = new Schema({
name: {type : String},
extrainfo: {type: Schema.Types.ObjectId, ref: 'Extrainfo'},
})
mongoose.model('Item', ItemSchema);
the subdocument:
var ExtrainfoSchema = new Schema({
this: {type : String},
that: {type: String},
})
mongoose.model('Extrainfo', ExtrainfoSchema);
how I hoped to be able to save:
Item.findOne({ name: 'whatever' }).populate('extrainfo').exec(function (err, item) {
item.extrainfo.this = "something diferen" //some random change in subdocument
item.save(function(){console.log("yay")}
});
Is there a way to do this whithout having to save to the document and the subdocument individually ?
Is there a way to do this whithout having to save to the document and the subdocument individually ?
No. mongodb will write to one and only one collection during a given write operation, and mongoose doesn't have any feature as far as I know to provide a single-callback facility to write to multiple collections.

Use mongoose populate to "join" matching subrecords

Using node.js, mongoose (3.5+), mongodb. Have got two collections in the DB:
var AuthorSchema = new mongoose.Schema({
name: { type: String },
});
var StorySchema = new mongoose.Schema({
title: { type: String },
author: { type: type: Schema.Types.ObjectId },
});
What I would like to do is retrieve an author and populate it with a subcollection (say, "stories") that is looked up from Story and match the author. Yes, much like a SQL join.
All of the examples out there work on the AuthorSchema having an array of objectids that reference StorySchema objects - that works fine. But I want to go the opposite direction; partly due to minimizing insert/updates. If I follow the example, adding a new store requires adding a new Story document and updating the Author. I want to just insert a new Story that references the Author.
I suspect that populate() is the right way to go, but can't get it to work. I'm doing something like this:
Author.find({name: 'Asimov').populate({
path: 'stories',
model: 'Story',
match: {'author': this['_id']},
}).exec(function(err, authors) {
console.log(authors);
})
But this doesn't return any stories member in the returned authors. Is this not a populate() solution? Do I really need to structure the schemas differently? Or is there some other feature of mongoose/mongo that would do what I'm looking for.
In the story schema, do this:
author: { type: type: Schema.Types.ObjectId, ref:'Author' }, //or whatever the model name is
then you can run
Story.find({}).populate('author').exec(function(err,stories) {...});

Default value for all queries via mongoose

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).

Resources