Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose: Setting two-way references

I'm working on an Express+Mongoose web app which has a number of models that depend on one another. Barring the possibility that I may not have designed my data correctly, I am looking to find out what is the best way for setting references between two objects that are related to one another.

I know that in my Schema obj, if I want a Schema to reference another Schema, I add referenceId: { type: Schema.Types.ObjectId, ref: 'ModelName' }, as a property. So far, when creating a model, I've been setting this property to the id I want to reference. For example: obj.referenceId = someReferenceId; obj.save(...).

However, what if I have two independent schemas but they each have a reference to one another? What is the best way of setting these?

Right now, I am doing the below, but the code looks very messy:

objectASchema = Schema({
    objectBReference: { type: Schema.Types.ObjectId, ref: 'ObjectB' },
})
mongoose.model('ObjectA', objectASchema);

objectBSchema = Schema({
    objectAReference: { type: Schema.Types.ObjectId, ref: 'ObjectA' },
})
mongoose.model('ObjectB', objectBSchema);

// in a route/controller, and assuming we have an Object B created
// but you can see that if I don't have that, it gets messier
ObjectA.create(..., function(err, objectA) {
    objectA.objectBReference = objectBReferenceId;
    objectA.save(function(err) {
        ...
        ObjectB.findById({_id: objectBReferenceId}, function(err, objectB) {
            objectB.objectAReference = objectA._id;
            objectB.save(function(err) {
                if (!err) {
                    // success! we're done here...
                }
            });
        })
    });
})

I am purposefully avoiding embedding in this case because in my app, I am operating under the assumption that I need to query these objects separately, without having to load the data from either.

My use case is akin to having something like a Product, Review, and User model, where each of these references the other, but is not necessarily embedded in the other. Queries can be something like: Get all the reviews for this product, or all the reviews from this user. This means reviews cannot be embedded in a User or Product.

like image 674
tsurantino Avatar asked Dec 25 '22 17:12

tsurantino


1 Answers

With MongooseJS you can create both of them locally, associate them and then save both to the DB in parallel using a promise (this uses the native Promise object but an library would work):

var objectA = new ObjectA();
var objectB = new ObjectB();

objectA.objectBReference = objectB;
objectB.objectAReference = objectA;

Promise.all([
  objectA.save(),
  objectB.save()
]).then(function(savedObjects) {
  // Do something to celebrate?
}).catch(function(err) {
  // one or both errored
});

UPDATE: You can also use nested saves if desired. The key point is that the IDs are generated locally and can be associated to other documents without saving them first.

var objectA = new ObjectA();
var objectB = new ObjectB();

objectA.objectBReference = objectB;
objectB.objectAReference = objectA;

objectA.save(function (err, objA) {
  if (err) { ... }

  objectB.save(function (err, objB) {
    if (err) { ... }

    // Do something to celebrate?
  });
});
like image 85
Jason Cust Avatar answered Jan 06 '23 11:01

Jason Cust