Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose: How to model a foreign key/inverse relationship?

I am using Mongoose to model Person and Transaction collections, where each Transaction will have references to two different Person instances:

var TransactionSchema = new Schema({
  , amount          : { type: Number, required: true }
  , from            : { type: ObjectId, required: true }
  , to              : { type: ObjectId, required: true }
  , date            : Date
});

var PersonSchema = new Schema({
    name            : { type: String, required: true }
  , transactions    : [ObjectId]
});

I'd like each Person to have a collection of all the Transactions that they are either the to or from value for. So far, this is the best way I've been able to figure out how to do it:

TransactionSchema.pre('save', function(next, done) {
    var transaction = this;

    Person.findById(this.to, function (err, person) {
        person.transactions.push(transaction);
        person.save();
    });

    Person.findById(this.from, function (err, person) {
        person.transactions.push(transaction);
        person.save();
    });

    next();
});

This seems excessive. Is there a better way to do it, or am I trying to use MongoDB too much like a relational database? Instead of having a collection of Transactions associated with each Person instance, should I just be querying the Translation collection directly?

Thank you.

like image 436
Bryan Irace Avatar asked Oct 30 '11 19:10

Bryan Irace


1 Answers

You've got to think more on the queries you are going to execute on the database when you design the MongoDB schema.

Try to duplicate data for speed and reference it for integrity. What does that mean?
Well, for example when you make a query for a Transaction, I guess you don't need all the user details from the first time no? (do you need the user's email, location when displaying info on a Transaction?)
I think you just probably need the user id and the username, so you should do something like this:

var TransactionSchema = new Schema({
  , amount          : { type: Number, required: true }
  , from            : { 
     user_id: {
       type: ObjectId
     , required: true
    }
   , username: {
       type: String
     , required: true
    } 
  }
  , to              : { 
     user_id: {
       type: ObjectId
     , required: true
    }
   , username: {
       type: String
     , required: true
    } 
  }
  , date            : Date
});

So instead of doing 3 queries for the page displaying the Transaction details (one for the transaction and 2 additional queries for the usernames), you'll have just one.
This is just an example, you could apply the same logic for the User schema, depending on what you're trying to achieve.

Anyway I don't think your middleware is ok, since you are not checking for errors there (you are always calling next no matter what). This is how I would write the middleware (didn't test, but the idea is important):

TransactionSchema.pre('save', function(next, done) {
  var transaction = this;

  Person.where('_id').in([this.to, this.from]).run(function (err, people) {
    if (people.length != 2) { next(new Error("To or from doesn't exist")); return; }
    Step(
      function save_to() {
        people[0].transactions.push(transaction);
        people[0].save(this);
      },
      function save_from(err) {
        if (err) { next(err); return; }
        people[1].transactions.push(transaction);
        people[1].save(this);
      },
      function callback(err) {
        next(err); 
      }
    );
  });
});

In the code above I'm using the Step library for flow control and I'm only using one query instead of two (when searching for "to" and "from").

like image 134
alessioalex Avatar answered Nov 02 '22 14:11

alessioalex