1

I'm building a sails.js api with MongoDb as my db.

Those are my models (simplified just for the sake of this question):

models/Passport.js simplified:

identifier: {
      type: 'string'
 },

owner: {
      model: 'User',
      required: true
 },

models/User.js simplified:

username: {
        type: 'string',
        unique: true
 },
passports: {
        collection: 'Passport',
        via: 'owner'
}

The problem

In a sails.js service login function I first try to get the user by a username from the User collection.

sails.models.user.findOne({username: 'demo'}).exec(function onExec(error, user)      {
    user; //contains user info
}

So far so good.

Then when the user is fetched (which is successfull), I grab his user.id and try to fetch his passport from the Passport collection.

sails.models.passport.findOne({owner:user.id}).exec(function onExec(error, passp) {
    if(passp==undefined)    //puppies dying in this line!!!!
        throw new Error('Can't find this passport');
    }
}

The returned passp object is undefined. What... WHY?

Additional info:

Running > db.passport.find({owner:"theCorrectUserId"}) in my mongo shell DOES return the correct passport thus its not a matter of an incorrect id, and also, running :

sails.models.passport.find().exec(function onExec(error, passports) {
    passports; //does contain all my passports
}

does contain all my passports. Thus its not a programming error either!

Aha moment:

What I've found over hours of banging my head on the wall is that removing the association and thus making my models like so:

models/Passport.js simplified:

identifier: {
      type: 'string'
 },

owner: {
      type: 'string'
      required: true
 }

models/User.js simplified:

username: {
        type: 'string',
        unique: true
 }

does solve the problem, and makes my passp object fetch the correct data..

Am I using Waterline associations wrong? But then why does it work in mongo shell when the association exists?

Do I even need associations since I'm using 2 different queries in my service? What about performance?

Yann Bertrand
  • 3,084
  • 1
  • 22
  • 38
SudoPlz
  • 20,996
  • 12
  • 82
  • 123

2 Answers2

3

Let's use the first models architecture.

This is how you should do it with Waterline:

sails.models.user
    .findOne({ username: username })
    // The populate method allows you to get associated data
    .populate('passports') 
    .exec(function (err, user) {
        // Handle errors
        if(err) return next('An unknown error has occured');
        if(!user) return next('Can\'t find this user');

        if(!user.passports || !user.passports.length)
            return next('This user has no passports');

        // The User has at least one passport
        ...

        return next(null, user);
}

The next method takes you back to your controller. Here is how you should use your service:

sails.services.myService
    .login(username, password, function (err, user) {
         ...
});

As you can see, there is only one query left :-).

Here is the docs about associations with the Sails ORM.

Yann Bertrand
  • 3,084
  • 1
  • 22
  • 38
  • Thanks this did work. Quick question, is this better than running 2 queries performance wise? – SudoPlz May 28 '15 at 12:39
  • I've never heard of such benchmarks... But it's absolutely the case for relational databases like MySQL. – Yann Bertrand May 28 '15 at 12:55
  • 1
    Maybe could you try to test both methods lots of time and record how much it took for your particular case? – Yann Bertrand May 28 '15 at 13:02
  • Also another problem I have with this particular solution is this: the `user.passports[0]` object is no longer of type models/Passport.js. Its a plain object. And from what I know theres NO typecasting in javascript. That is a problem for me, and thus I must return back on running 2 queries! – SudoPlz May 28 '15 at 17:00
  • I think you should open a new question. It could be interesting for a lot of people, including me :-). – Yann Bertrand May 28 '15 at 19:19
  • Ok, new question opened here: http://stackoverflow.com/questions/30526939/waterline-mongodb-populated-fetched-object-looses-its-type-and-prototype-funct – SudoPlz May 29 '15 at 10:13
1

Instead of doing:

sails.models.passport.findOne({owner:user.id})

try:

sails.models.passport.findOne({owner:user})
craigvl
  • 280
  • 2
  • 9