I am using mongodb as database with mongoose as ORM. I have a field booking_id in my schema which is unique , so I cannot have it null. Thus I have designed my code something like this.
var bookingSchema = new Schema({
booking_id_customer: {
type: Number,
default : Math.floor(Math.random()*900000000300000000000) + 1000000000000000,
index: { unique: true }
},
It works perfectly for the first time, but from 2nd time onwards I get this duplicacy error.
{ [MongoError: E11000 duplicate key error index: xx.bookings.$booking_id_customer_1 dup key: { : 4.439605615108491e+20 }]
name: 'MongoError',
message: 'E11000 duplicate key error index:
I expect it to generate random numbers but I have no clue about whats going wrong in 2nd time.
You are setting the default just once, at schema creation.
If you want it to be called for each new document, you need to turn it into a function that Mongoose will call:
default : function() {
return Math.floor(Math.random()*900000000300000000000) + 1000000000000000
}
However, there is another issue with your code: the values you're using (900000000300000000000 and 1000000000000000) exceed Number.MAX_SAFE_INTEGER, which can lead to problems.
I would suggest using mongoose.Types.ObjectId as id generator, which is also what Mongoose and MongoDB use to create (unique) document id's:
booking_id_customer : {
type : mongoose.Schema.Types.ObjectId,
default : mongoose.Types.ObjectId,
index : { unique: true }
}
Or re-use the _id property of the document, which is also unique.
Related
I am new to NodeJS and I am using NodeJS Framework HapiJs. I have parent-child schema of a Model "Elements" in which parent and child belongs to the same collection. The schema is similar to following:
var mschema = new Schema({
autoIncrementId: { type: Number,unique: true,index:true,sparse:true},
elementName: {
type: String,
required: true
},
elementCanvasId: {
type: String,
required: true
},
parentId: {
type: Schema.Types.ObjectId,
required: false,
ref: "Elements",
},
parentCanvasId: {
type: String,
required: false
},
});
var modules = Mongoose.model('Elements', mschema);
here elementCanvasId is generated at client side. And parentCanvasId is a reference to elementCanvasId of the parent element. Also its not necessary that every element will have a parent. Now based on elementCanvasId of element I wanna fetch all the elements that comes under hierarchy(i.e all its children elements and then children's subchildren and then their's subchildren and so on.) of that element in a Single Dimensional Array. I cannot use Schema.Types.ObjectId for this purspose as I only have to use elementCanvasId which is generated at client's side only. So, can someone help me out to get it done. Any kind of help is hugely appreciated. Thanks in advance.
I think what you are looking for is $graphLookup, however it can only be used from version 3.4+, it gets quite complicated
For full details see https://docs.mongodb.com/v3.4/reference/operator/aggregation/graphLookup/
Elements.aggregate( [
{
$graphLookup: {
from: "elements",
startWith: "$parentCanvasId",
connectFromField: "parentCanvasId",
connectToField: "elementCanvasId",
as: "elementHierarchy"
}
}
] )
You need to use $graphLookup. As $graphLookup performs a recursive search on a collection.
Elements.aggregate([{ $graphLookup : {
from : 'elements', // MongoDB collection name not mongoose schema name
startWith : '$parentCanvasId', //it is expression which can include can include field paths and system variables, literals, expression objects etc.
connectFromField : 'elementCanvasId',
as : 'children'
} }])
If you want to fetch element hierarchy for only one element then you need to use $match in aggregation pipeline
Elements.aggregate([{ $match : { autoIncrementId : '1' } }, { $graphLookup : {
from : 'elements', // MongoDB collection name not mongoose schema name
startWith : '$parentCanvasId',
connectFromField : 'elementCanvasId',
as : 'children'
} }])
Note : autoIncrementId : 1 is the condition by which you can fetch the result for single element
Let's say we have a Mongoose schema in our Node.js project:
let coolSchema = new mongoose.Schema({
field_1 : Number,
field_2 : String,
field_3 : [ String ],
});
And let's we have an according object:
var data = {
field_1 : 123,
field_2 : 'blah',
field_3 : ['aa', 'bb'],
};
Now to save this data into MongoDB we can use this code:
let Model = require('mongoose').model('CoolModel', coolSchema);
(new Model(data)).save();
Ok, while it's all cool.
But if data does not contain field_3 (array field, and the same will be for an object field) Mongoose will anyway add this field into the being created document with empty value.
Can we somehow tell Mongoose not to create this field if it's not contained in the data object?
you can do it easily skip the array field and array of object field.. This will let you skip saving empty array in new documents.but you have to use pre hook for this .
var brandSchema = new Schema({
name : {type:String},
email:String,
check:[]
})
brandSchema.pre('save', function (next) {
if (this.isNew && 0 === this.check.length) {
this.check = undefined;
}
next();
})
when new document is inserted in your schema you have to use this middlware.this works fine so try this.
this is the response when we want to insert any document
"data": {
"__v": 0,
"name": "testing",
"email": "testing#gmail.com",
"_id": "5915b018e292833edda8837f"
}
so i have send only email and name but check(array) field is skipped(Not send any value).
The accepted answer is good. But if you wouldn't want to use pre-hook, then you can add default: undefined to the array fields. For example:
var schema = new Schema({
myArr: { type: [String], default: undefined }
});
Refer to this comment for more explanation.
Not particularly an answer to the question itself but some thought on the matter.
It's not clear exactly what you're trying to achieve here. You defined a schema that is supposed to contain a list of string. Mongoose correctly does so that the data saved in your schema is consistent with the definition of the schema.
In this case, the list is more of a structural part of the schema. If you have different behaviour, you'd have to handle special case in your code in case the list isn't present. Now, you can safely assume that you schema is always returning a list so fetching some data will always allow you to do:
coolData.field_3.forEach(function(x) {
do_cool_things(x)
})
What you're asking is to make the schema allow inconsistent data being returned from mongodb... In other words, you'd have to do this in order to prevent accessing attributes on undefined:
if (coolData.field_3) {
coolData.field_3.forEach(function(x) {
do_cool_things(x)
})
}
Also, I you were trying to optimize the size of you objects/database, you could fill a bug report so mongoose doesn't define empty values while saving the objects and autofill them with defaults when the field is missing from mongodb. (I could be wrong but did you actually check if the data in mongodb was containing empty values or you were just looking at data coming from mongoose?)
It's because you're not marking the fields as required in your schema definition.
Do this:
let coolSchema = new mongoose.Schema({
field_1 : { type: Number, required: true },
field_2 : { type: String, required: true },
field_3 : { type: [ String ], required: true },
});
I'll break the problem down to make sure I've explained it well.
My web app is using MEAN.js, in this case I'm implementing an update function in the mongoose server side controller.
using this schema :
client side angular controller code is working fine, and it is sending me the right object to insert it in mongodb
I have an array of a custom object consisting of other related schema objects in the database, it expresses a list of updates referencing a task object, a change in Taskstate object, and the time updates happened.
tupdates:[{
task:{
type: Schema.ObjectId,
ref: 'Task'
},
tstate:{
type: Schema.ObjectId,
ref: 'Tstate'
},
startedat: Date,
stoppedat: Date,
updatedby:{
type: Schema.ObjectId,
ref: 'User'
},
}]
I implemented a function that would loop over the array in the req object, and creates custom objects for each update in the array, and finally inserts it in the database.
Server Side mongoose controller
var mongoose = require('mongoose'),
Job = mongoose.model('Job');
exports.update = function(req, res){
var job = req.job;
//check for updates
if(req.body.tupdates.length>0){
for (var j=0; j<req.body.tupdates.length; j++){
var theobj = req.body.tupdates[j];
var tupdate = ({
task : theobj.task._id,
tstate: theobj.tstate._id
});
job.tupdates.push(tupdate);
}
}
job.save(function(err){
if(err){
return res.status(400).send({
message: getErrorMessage(err)
});
}else{
res.json(job);
}
});
};enter code here
The data is persisted in the database, the problem is that an additional _id value with ObjectID I have no reference for is inserted for each update in the req array as follows :
db.jobs.find()
{ "_id" : ObjectId("56eff14d4b343c7f0a71f33c"), "creator" : ObjectId("55ddd115a2904424680263a0"), "title" : "Job1", "tupdates" : [ { "task" : ObjectId("567a9c4b90f3ccd10b0e7099"), "tstate" : ObjectId("5693bb0f804936f167fe9ec2"), *"_id" : ObjectId("56eff38e095a2fa41312d876")*} ]}
I added these stars to indicate the _id value with ObjectId reference that gets added to the object i create in the server side controller, the problem persists everytime I do an update on the same object. It creates 500 errors later, and must be omitted for the function to work well in production, I appreciate the help, thinking it's a java script tweak i need to make.
By default mongoose adds _id field to array elements if they are objects.
Just add _id: false to your schema:
tupdates:[{
_id: false,
task:{
type: Schema.ObjectId,
ref: 'Task'
},
tstate:{
type: Schema.ObjectId,
ref: 'Tstate'
},
startedat: Date,
stoppedat: Date,
updatedby:{
type: Schema.ObjectId,
ref: 'User'
},
}]
My API currently has a route for Getting an event from my MongoDB database based on event_id. This works fine. However, I have a 'photos' array within this event object that is growing (currently over 3,000 objects within this array).
I want to pass a limit parameter to limit the number of results pulled from this array, but cannot figure out how. Below is my current node route and mongoDB schema:
route:
// get event by _id
app.get('/api/events/:event_id', function(req, res) {
// use mongoose to get event
Event.findOne({object_id: req.params.event_id}, function(err, event) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
if (req.params.limit >= 0) {
// res.jsonp(event) with photos array limited to req.params.limit
}
res.jsonp(event); // return event in JSON format
});
});
schema:
var eventSchema = new Schema({
event: String,
city: String,
state: String,
date: String,
start: String,
end: String,
dateState: String,
radius: String,
team_1: String,
team_2: String,
object_id: String,
longitude: String,
latitude: String,
cover: {
img: String,
username: String
},
photos: []
})
Don't have a constantly growing array field. It's not good for performance because MongoDB (well, if <= 2.6/using mmap) will be moving the document around when it grows outside of the space allocated for it by the storage engine, causing performance problems. You should change your schema to avoid an array like this, but I can't really say more about how you should do it because I don't know much about your use case.
There is a way to limit the number of array elements returned in a find query though, using $slice projection.
> db.test.drop()
> db.test.insert({ "_id" : 0, "x" : [0, 1, 2, 3, 4] })
> db.test.find({ "_id" : 0 }, { "x" : { "$slice" : 2 } })
{ "_id" : 0, "x" : [0, 1] }
Event.findOne({object_id: req.params.event_id})
.limit(10)
.exec(function(e,doc){
...
});
Edit
Or if you have ref on the photo... you can populate the doc array of referenced id with limit option. Hope it helps :) All abount population
.find(...)
.populate({
path: 'photos',
options: { limit: 5 }
})
.exec(...)
Schema
var eventSchema = new Schema({
event: String,
city: String,
state: String,
...
photos: [{ type:String, ref:'pictureSchema' }]
}
var pictureSchema = new Schema({
name : {type:String},
url : {type:String},
...
}
In photos array than you just put id of the pictures doc, when you populate the photos array it will put pictureSceham doc insted of _id.
I'm looking for an easy way of updating an embedded document using mongoose without having to set each specific field manually. Looking at the accepted answer to this question, once you find the embedded document that you want to update you have to actually set each respective property and then save the parent. What I would prefer to do is pass in an update object and let MongoDB set the updates.
e.g. if I was updating a regular (non embedded) document I would do this:
models.User.findOneAndUpdate({_id: req.params.userId}, req.body.user, function(err, user) {
err ? resp.status(500).send(err) : user ? resp.send(user) : resp.status(404).send();
});
Here I don't actually have to go through each property in req.body.user and set the changes. I can't find a way of doing this kind of thing with sub documents as well ?
My Schema is as follows:
var UserSchema = BaseUserSchema.extend({
isActivated: { type: Boolean, required: true },
files: [FileSchema]
});
var FileSchema = new mongoose.Schema(
name: { type: String, required: true },
size: { type: Number, required: true },
type: { type: String, required: true },
});
And I'm trying to update a file based on user and file id.
Do I need to create a helper function to set the values, or is there a MongoDB way of doing this ?
Many thanks.
Well presuming that you have something that has you "filedata" in a variable, and of course the user _id that you are updating, then you wan't the $set operator:
var user = { /* The user information, at least the _id */
var filedata = { /* From somewhere with _id, name, size, type */ };
models.User.findOneAndUpdate(
{ "_id": user._id, "files._id": filedata._id },
{
"$set": {
"name": filedata.name,
"size": filedata.size,
"type": filedata.type
}
},
function(err,user) {
// Whatever in here such a message, but the update is already done.
}
);
Or really, just only $set the fields that you actually mean to "update" as long as you know which ones you mean. So if you only need to change the "size" then just set that for example.