Mongoose Index on a field in nested document - node.js

I have a small schema
var PostSchema = new mongoose.Schema({
title: String,
link: String,
author: {type:String,required:true},
upvotes: {type: Number, default: 0},
nesteddoc : {
field1: String
}
});
//This is broken - index on field1
PostSchema.index({nesteddoc.field1:1},{unique:true});
Is it possible to have an index on nested field by specifying in Mongoose schema and not running a MongoDB query to ensure the index ?

Use quotes around "nesteddoc.field1" to evaluate the nested field :
PostSchema.index({ "nesteddoc.field1": 1 }, { unique: true });
Furthermore, mongoose will call ensureIndex internally, from mongoose doc :
When your application starts up, Mongoose automatically calls
ensureIndex for each defined index in your schema. Mongoose will call
ensureIndex for each index sequentially, and emit an 'index' event on
the model when all the ensureIndex calls succeeded or when there was
an error. While nice for development, it is recommended this behavior
be disabled in production since index creation can cause a significant
performance impact. Disable the behavior by setting the autoIndex
option of your schema to false, or globally on the connection by
setting the option config.autoIndex to false.
You can also define index in schema :
var PostSchema = new mongoose.Schema({
title: String,
link: String,
author: { type: String, required: true },
upvotes: { type: Number, default: 0 },
nesteddoc: {
field1: { type: String, unique: true, index: true },
}
});

Related

Unique in mongoose not working as expected

I have my example mongoose schema as below
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const exampleSchema = new Schema ({
name:{
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
mobile:{
type: String,
required: true,
unique: true
}
})
module.exports ={Driver: mongoose.model('Driver', driverSchema)}
Now the thing is that unique in mobile is working fine, but in email it allows me to insert duplicate email address.
You had better to create your indexes in mongodb shell.
In the mongoose docs they state:
In a production environment, you should create your indexes
using the MongoDB shell rather than relying on mongoose to do it for
you. The unique option for schemas is convenient for development and
documentation, but mongoose is not an index management solution.
So I would remove unique options in schema, and can create the unique indexes in mongodb shell like this:
db.drivers.createIndex( { "email": 1 }, { unique: true } )
db.drivers.createIndex( { "mobile": 1 }, { unique: true } )
It was due to I have records in collection before giving unique: true.

How to populate a document inside an array inside another document?

Sorry if title looks complicated... I couldn't think of a better way to describing it.
My real case situation matches the following Schemes:
Collection1:
const mongoose = require('mongoose');
const itemSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
quantity: { type: Number, required: [true, 'Quantity is required.'] },
collection2: { type: mongoose.Schema.Types.ObjectId, ref: 'Collection2' }
}, { _id : false });
const collection1Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
imagePath: { type: String, required: [true, 'Image is required.'] },
items: [itemSchema]
});
module.exports = mongoose.model('Collection1', collection1Schema);
Note: itemsSchema is inside the collection1 file (and having no declared _id's) because they only exist for the Collection1 model (considering "quantity" and other fields I removed for simplification). This itemsScheme is not needed elsewhere as another collection.
Collection2:
const mongoose = require('mongoose');
const collection2Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'], unique: true }
});
module.exports = mongoose.model('Collection2', collection2Schema );
Note: Other properties (such as 'imagePath') were removed for simplification.
Now, this is the query I am trying to run:
Collection1.find()
.populate({
path: 'items',
populate: {
path: 'collection2', model: 'Collection2'
}
})
.then(...)
.catch(...);
And this is the error message I am getting when I run it:
Error fetching collection1: Cast to ObjectId failed for value "{
name: 'an item name',
quantity: 750
}" at path "_id" for model "Collection1"
The exact same error happens if I just run:
Collection1.find()
.populate('items')
.then(...)
.catch(...);
Maybe I cannot run .populate('items') because it has no declared model. If this is the case, how can I populate collection2 while querying collection1? Again, I cannot consider storing items in a separated collection.
But if I run:
Collection1.find()
.populate('collection2')
.then(...)
.catch(...);
I get the items, no errors, but it doesn't populate collection2. Well, it makes sense for the items because they're just an array of a block of properties inside collection1. But what about populating collection2?
Collection2 already has a few documents added, all with their _ids and other fields well filled. In the controller, I set _id: new mongoose.Types.ObjectId(), while creating a new document for both cases, Collection1 and Collection2.
In the front-end, I create a new document for Collection1, I add items, each item with a document from Collection2, and I save everything with no errors. I also confirmed everything is been properly saved (collection1 has list of items and each item an _id reference to collection2). The only problem is populating collection2 inside this array.
I have already tried restructuring everything with _ids (including itemScheme) and dropping all collections to test it again but no success.
I have been stuck with this problem for about three days now.
Is there any special property I should be setting for populate to make it work for this specific structure?
Thanks in advance...
populate('items')
This will not work as item is not a model.
What you want is following:
Collection1.find()
.populate('items.collection2')
.then(...)
.catch(...);
This will populate collection2 in all the array elements

Return Mongo document using Mongoose where subdocument does NOT exist?

Help! I'm losing my mind. I need to simply return a Mongo document, using Mongoose, IF a sub document does not exist.
My schemas:
var userSchema = new mongoose.Schema({
email: {type: String, unique: true, lowercase: true},
password: {type: String, select: false},
displayName: String,
picture: String,
facebook: String,
deactivation: deactiveSchema
});
var deactiveSchema = new mongoose.Schema({
when : { type: Date, default: Date.now, required: true },
who : { type: Schema.Types.ObjectId, required: true, ref: 'User' }
});
My goal is to lookup a user by their facebook ID if they have not been deactivated.
If they have been deactivated, then a deactivation subdocument will exist. Of course, to save space, if they are active then a deactivation will not exist.
On a side note, I'm also worried about how to properly construct the index on this logic.
I'd post snippets but every attempt has been wrong. =(
You can use $exists operator:
userSchema.find({deactivation:{$exists:false}}).exec(function(err,document){
});
or $ne:
userSchema.find({deactivation:{$ne:null}}).exec(function(err,document){
});
Since you are retiring data and not deleting, I'd go with one of two approaches:
Flag for retired (Recommended)
add to your schema:
retired: {
type: Boolean,
required: true
}
and add an index for this query:
userSchema.index({facebook: 1, retired: 1})
and query:
User.find({facebook: facebookId, retired: false}, callback)
Query for existence
User.find().exists("deactivation", false).exec(callback)
The latter will be slower, but if you really don't want to change anything, it will work. I'd recommend taking some time to read through the indexing section of the mongo docs.
Mongoose has many options for defining queries with conditions and a couple of styles for writing queries:
Condition object
var id = "somefacebookid";
var condition = {
facebook : id,
deactivation: { $exists : true}
};
user.findOne(condition, function (e, doc) {
// if not e, do something with doc
})
http://mongoosejs.com/docs/queries.html
Query builder
Alternatively, you may want to use the query builder syntax if you are looking for something closer to SQL. e.g.:
var id = "somefacebookid";
users
.find({ facebook : id }).
.where('deactivation').exists(false)
.limit(1)
.exec(callback);

Mongoose does not create text index

I have the following schema:
entrySchema = new mongoose.Schema({
size: { type: Number },
title: {type: String, trim: true },
content: { type: String, trim: true },
tags: { type: [String], trim: true, index: true },
author: { type: String, trim: true, index: true }
});
entrySchema.index({ title: "text", content: "text" });
module.exports = mongoose.model('Entry', entrySchema);
The problem is that mongoose does not create the text indexes. The indexes for tags and author are created correctly, though.
Am I using the index() function in a wrong way?
I don't get any errors in the mongod session. It logs successful index creation for the non-text indexes, but it seems as if mongoose never calls ensureIndex for the text indexes.
After debugging as described in Mongoose Not Creating Indexes (thanks to #JohnyHK for the link) I saw that the actual problem was not the text index.
I was using the mongoose-auto-increment plugin and that resulted in errors indexing the _id field.
The solution was to have autoIncrement not use the _id field but a separate field like this:
entrySchema.plugin autoIncrement.plugin, {
model: 'Entry'
startAt: 1000
field: 'shortId'
}
I just did not thing about that because indexing worked fine without the text index. There seems to be some kind of incompatibility with the plugin and text indexes.

Using UUIDs in mongoose for ObjectID references

I'm building a CRUD-style REST service with Node.js, Express and MongoDB using mongoose. This service is going to allow users of an already existing android application to upload/sync the contents of their individual databases online.
The data model for the already-existing application uses UUIDs (generated in Java) which clashes with the shorter, monotonic MongoDB style _id fields. Because the data model already exists and is populated with data from many users, I cannot convert the source data over to monotonic MongoDB-style _ids. This has left me with 2 options that I can think of: either 1) Make Mongo/Mongoose (or some other ODM) play nicely with full UUIDs instead of the monotonic _ids or 2) add a uuid field to the mongoose model in addition to the _id field and fight the pitfalls of this approach. I'm attempting to choose option #1 and running into issues with ObjectID references.
I originally stumbled upon mongoose-uuid, but unfortunately this isn't working for my use-case properly because it was overwriting my explicitly-set _id value when creating new Mongoose objects. Diving into the plugin code, it assumes that an object is new (by calling checking Mongoose's .isNew value) and thus overwrites the _id with a new uuid. Since I need to retain the original uuid when creating new documents in Mongo, this plugin isn't working for me.
Next, I found a post by Aaron Heckmann, creator of mongoose, on a similar topic. This has been helpful, however I am now encountering the problem where I cannot have my mongoose schemas reference each other by ObjectID, since they technically they are now referencing each other using String `_ids.
Schema example:
var mongoose = require('mongoose');
var uuid = require('node-uuid');
var Schema = mongoose.Schema;
var trackPassSchema = new Schema({
_id: { type: String, default: function genUUID() {
uuid.v1()
}},
//Omitting other fields in snippet for simplicity
vehicle: [
{type: Schema.Types.ObjectId, required: true, ref: 'Vehicle'}
]
});
module.exports = mongoose.model('TrackPass', trackPassSchema);
Referencing schema:
var mongoose = require('mongoose');
var uuid = require('node-uuid');
var Schema = mongoose.Schema;
var vehicleSchema = new Schema({
_id: { type: String, default: function genUUID() {
uuid.v1()
}},
//Omitting other fields in snippet for simplicity
description: {type: String},
year: {type: Number}
});
module.exports = mongoose.model('Vehicle', vehicleSchema);
When I attempt to call save() a trackPass that has been passed in from my application:
var trackPass = new TrackPass(req.body);
//Force the ID to match what was put into the request
trackPass._id = req.params.id;
trackPass.save(function (err) { ... }
I get the following error:
{ [CastError: Cast to ObjectId failed for value "b205ac4d-fd96-4b1e-892a-d4fab818ea2a" at path "vehicle"]
message: 'Cast to ObjectId failed for value "b205ac4d-fd96-4b1e-892a-d4fab818ea2a" at path "vehicle"',
name: 'CastError',
type: 'ObjectId',
value: ["b205ac4d-fd96-4b1e-892a-d4fab818ea2a"],
path: 'vehicle' }
I believe this error makes sense as I'm now using Strings which are longer than typical Mongo ObjectIDs. Without having the ObjectID reference, I don't believe I will be able to populate() referenced objects from other collections. I suppose I could simply not reference the other nested objects in my schema definitions, however I don't like this approach as I feel I will be losing a lot of the benefit of utilizing the ODM. Any other thoughts?
You can still use populate() with _id values of types besides ObjectID, but you do need to use the same type in the reference definition.
So your trackPassSchema would need to change to:
var trackPassSchema = new Schema({
_id: { type: String, default: function genUUID() {
return uuid.v1()
}},
vehicle: [
{type: String, required: true, ref: 'Vehicle'}
]
});
As Adam notes in the comments, you could simplify your default value to:
var trackPassSchema = new Schema({
_id: { type: String, default: uuid.v1 },
vehicle: [
{type: String, required: true, ref: 'Vehicle'}
]
});
Both JohnnyHK and Adam C answers are correct. But if you're using uuid in schema for an array of objects, it is good to use it like this
var trackPassSchema = new Schema({
_id: { type: String, default: () => uuid.v1 },
vehicle: [
{type: String, required: true, ref: 'Vehicle'}
]
});
Because, in one such scenario when i tried using like this _id: { type: String, default: () => uuid.v1 } multiple objects of the array had the same id.
It is not possible in this case as _id is unique field, but it can happen when you are using with fields that aren't unique.

Resources