9

Sails.js .10 rc8

I've completely run out of ideas for this

I have a model called User and I want to associate it in a collection to other users like a friends list. Like a many-to-many association

   //User.js
   friends: {
     collection: 'user',
     via: 'friends'
   }

but when I run .populate('friends') it doesn't populate anything. Any ideas?

Joe Hill
  • 333
  • 3
  • 12
scott
  • 583
  • 6
  • 11
  • Did you ever find a solution for this? I'm currently attempting to do exactly the same thing.. – Michael Feb 23 '15 at 04:47
  • 1
    Hi Michael, here's the issue, since it's the same model, that means they both have a many to many association. Waterline auto creates a join table, but it messes up the dominance. So you can't use the populate function on it. The best way I've found do this is to create another model (ex. Friends.js) and have a user model and then a collection of other users models. Then I set up a hook that 'populates' that when I need it. – scott Feb 23 '15 at 15:12
  • 1
    Thanks scott! That's exactly where I was heading, but glad to hear it actually worked for you. I'll keep heading down this path. – Michael Feb 24 '15 at 03:23
  • @Michael Hey guys, just to follow up: The usage in this SO question _should_ work just fine if you omit `via`; but like Scott pointed out, there was a lingering issue with this across a few different patch releases. That said, as far as I know, this is now resolved in Sails v0.12. So if you're still seeing inconsistent behavior, I'd like to know, and I'm happy to help! Please chime in [on issue #1405](https://github.com/balderdashy/waterline/issues/1405), which lists the [cases we've tested thus far](https://github.com/balderdashy/waterline/issues/1405#issuecomment-272856434). – mikermcneil Jan 16 '17 at 13:06

4 Answers4

3

Your models should look like this...

//User.js
friends: {
  collection: 'friend',
  via: 'user'
}

//Friend.js
user: {
  model: 'user'
  via: 'friends'
}

Also sails 10 rc8 is old. You should be on sails 10.5

InternalFX
  • 1,475
  • 12
  • 14
3

I find that the best way to implement this is actually adding a reference to the id.

Check this User model:

module.exports = {
  attributes: {
    name: {
      type: 'string',
      required: true,
      minLength: 2
    },
    email: {
      type: 'email',
      required: true,
      unique: true
    },
    friends: {
      collection: 'user',
      via: 'id'
    }
  }
};

Now, if you run sails console you can test the following commands:

User.create({name:'test',email:'test@test.com'}).exec(console.log);

It returns:

{ name: 'test',
  email: 'test@test.com',
  createdAt: '2016-12-01T22:06:19.723Z',
  updatedAt: '2016-12-01T22:06:19.723Z',
  id: 1 }

You created your first user. Lets create some other ones:

User.create({name:'test2',email:'test2@test.com'}).exec(console.log);

Resulting in:

 { name: 'test2',
  email: 'test2@test.com',
  createdAt: '2016-12-01T22:06:40.808Z',
  updatedAt: '2016-12-01T22:06:40.808Z',
  id: 2 }

Let's see what we have so far:

User.find().populate('friends').exec(console.log);

[ { friends: [],
    name: 'test',
    email: 'test@test.com',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: 'test2@test.com',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 } ]

Now, let's create a user with a friend, using the reference to the first user. Notice that I just pass a single id, not an array:

User.create({name:'test3',email:'test3@test.com', friends:1}).exec(console.log);

Now, the result is this one. Notice that "friends" does not appear. This is by design.

{ name: 'test3',
  email: 'test3@test.com',
  createdAt: '2016-12-01T22:07:34.988Z',
  updatedAt: '2016-12-01T22:07:34.994Z',
  id: 3 }

Let's do a find with populate to see the current status:

User.find().populate('friends').exec(console.log);

Now we see that the third user has friends. The others have an empty array.

 [ { friends: [],
    name: 'test',
    email: 'test@test.com',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: 'test2@test.com',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: 'test@test.com',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 } ],
    name: 'test3',
    email: 'test3@test.com',
    createdAt: '2016-12-01T22:07:34.988Z',
    updatedAt: '2016-12-01T22:07:34.994Z',
    id: 3 } ]

Let's create a fourth one, this time with two friends:

User.create({name:'test4',email:'test4@test.com', friends:[1,2]}).exec(console.log);

Resulting in (again, no friends property):

 { name: 'test4',
  email: 'test4@test.com',
  createdAt: '2016-12-01T22:07:50.539Z',
  updatedAt: '2016-12-01T22:07:50.542Z',
  id: 4 }

This time, we passed an array of ids to friends. Let's see the current status:

User.find().populate('friends').exec(console.log);

 [ { friends: [],
    name: 'test',
    email: 'test@test.com',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: 'test2@test.com',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: 'test@test.com',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 } ],
    name: 'test3',
    email: 'test3@test.com',
    createdAt: '2016-12-01T22:07:34.988Z',
    updatedAt: '2016-12-01T22:07:34.994Z',
    id: 3 },
  { friends: 
     [ { name: 'test',
         email: 'test@test.com',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 },
       { name: 'test2',
         email: 'test2@test.com',
         createdAt: '2016-12-01T22:06:40.808Z',
         updatedAt: '2016-12-01T22:06:40.808Z',
         id: 2 } ],
    name: 'test4',
    email: 'test4@test.com',
    createdAt: '2016-12-01T22:07:50.539Z',
    updatedAt: '2016-12-01T22:07:50.542Z',
    id: 4 } ]

Now, how do you add a friend to an existing user? It's somehow odd: you have to first find the user, then use the add function on the resulting model, and then, save it. In my day to day code, I have a helper function that does just that. So, here is the example:

User.findOne(1).populate('friends').exec(function(err,u){ u.friends.add(3);u.save(function(err){ if(err) console.error(err);});});

Now in the console we see no results. Let's check the User content now:

User.find().populate('friends').exec(console.log);

[ { friends: 
     [ { name: 'test3',
         email: 'test3@test.com',
         createdAt: '2016-12-01T22:07:34.988Z',
         updatedAt: '2016-12-01T22:07:34.994Z',
         id: 3 } ],
    name: 'test',
    email: 'test@test.com',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:09:41.410Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: 'test2@test.com',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: 'test@test.com',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:09:41.410Z',
         id: 1 } ],
    name: 'test3',
    email: 'test3@test.com',
    createdAt: '2016-12-01T22:07:34.988Z',
    updatedAt: '2016-12-01T22:07:34.994Z',
    id: 3 },
  { friends: 
     [ { name: 'test',
         email: 'test@test.com',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:09:41.410Z',
         id: 1 },
       { name: 'test2',
         email: 'test2@test.com',
         createdAt: '2016-12-01T22:06:40.808Z',
         updatedAt: '2016-12-01T22:06:40.808Z',
         id: 2 } ],
    name: 'test4',
    email: 'test4@test.com',
    createdAt: '2016-12-01T22:07:50.539Z',
    updatedAt: '2016-12-01T22:07:50.542Z',
    id: 4 } ]

With this method, you can even add the same user to the friends collection!

User.findOne(1).populate('friends').exec(function(err,u){ u.friends.add(1);u.save(function(err){ if(err) console.error(err);});});

Have fun!

Luis Lobo Borobia
  • 994
  • 11
  • 25
0

You should use .populate('friends') instead of .populate(friends)

haotang
  • 5,520
  • 35
  • 46
0

As it turns out, when you have a many to many association in waterline you have to declare one of the models as dominant. Since they are the same model, neither is dominated. While a join table is created, there is no way to populate it.

scott
  • 583
  • 6
  • 11
  • Dominant is used for cross-adapter queries, not for what you describe. http://sailsjs.com/documentation/concepts/models-and-orm/associations/dominance It just says where is the helper table going to be created – Luis Lobo Borobia Dec 01 '16 at 01:03
  • When you set dominate on a many to many association, you are correct, it does say where join table will be in the event of a cross adapter query. However, it also effects the naming order of the join table. Normal case `user_friends__friend_users` These names are important because they will be the name of the model and the attribute. However, these can not be in the same model (see OP comments). The join table comes out wrong because of a waterline bug. If I recall, the join table names comes out as user_friends__users_friends or something like that where the table `users` does not exists. – scott Dec 01 '16 at 18:41