19
votes

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?

2

2 Answers

29
votes

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'}
    ]
});
0
votes

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.