Sails.js Same Model many to many association
Asked Answered
T

4

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?

Toxophilite answered 7/10, 2014 at 3:20 Comment(4)
Did you ever find a solution for this? I'm currently attempting to do exactly the same thing..Novanovaculite
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.Toxophilite
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.Novanovaculite
@Novanovaculite 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, which lists the cases we've tested thus far.Polyunsaturated
S
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:'[email protected]'}).exec(console.log);

It returns:

{ name: 'test',
  email: '[email protected]',
  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:'[email protected]'}).exec(console.log);

Resulting in:

 { name: 'test2',
  email: '[email protected]',
  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: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    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:'[email protected]', friends:1}).exec(console.log);

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

{ name: 'test3',
  email: '[email protected]',
  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: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 } ],
    name: 'test3',
    email: '[email protected]',
    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:'[email protected]', friends:[1,2]}).exec(console.log);

Resulting in (again, no friends property):

 { name: 'test4',
  email: '[email protected]',
  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: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 } ],
    name: 'test3',
    email: '[email protected]',
    createdAt: '2016-12-01T22:07:34.988Z',
    updatedAt: '2016-12-01T22:07:34.994Z',
    id: 3 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 },
       { name: 'test2',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:40.808Z',
         updatedAt: '2016-12-01T22:06:40.808Z',
         id: 2 } ],
    name: 'test4',
    email: '[email protected]',
    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: '[email protected]',
         createdAt: '2016-12-01T22:07:34.988Z',
         updatedAt: '2016-12-01T22:07:34.994Z',
         id: 3 } ],
    name: 'test',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:09:41.410Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:09:41.410Z',
         id: 1 } ],
    name: 'test3',
    email: '[email protected]',
    createdAt: '2016-12-01T22:07:34.988Z',
    updatedAt: '2016-12-01T22:07:34.994Z',
    id: 3 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:09:41.410Z',
         id: 1 },
       { name: 'test2',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:40.808Z',
         updatedAt: '2016-12-01T22:06:40.808Z',
         id: 2 } ],
    name: 'test4',
    email: '[email protected]',
    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!

Semipermeable answered 1/12, 2016 at 22:22 Comment(0)
V
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

Velamen answered 8/10, 2014 at 22:29 Comment(0)
S
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:'[email protected]'}).exec(console.log);

It returns:

{ name: 'test',
  email: '[email protected]',
  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:'[email protected]'}).exec(console.log);

Resulting in:

 { name: 'test2',
  email: '[email protected]',
  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: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    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:'[email protected]', friends:1}).exec(console.log);

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

{ name: 'test3',
  email: '[email protected]',
  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: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 } ],
    name: 'test3',
    email: '[email protected]',
    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:'[email protected]', friends:[1,2]}).exec(console.log);

Resulting in (again, no friends property):

 { name: 'test4',
  email: '[email protected]',
  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: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:06:19.723Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 } ],
    name: 'test3',
    email: '[email protected]',
    createdAt: '2016-12-01T22:07:34.988Z',
    updatedAt: '2016-12-01T22:07:34.994Z',
    id: 3 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:06:19.723Z',
         id: 1 },
       { name: 'test2',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:40.808Z',
         updatedAt: '2016-12-01T22:06:40.808Z',
         id: 2 } ],
    name: 'test4',
    email: '[email protected]',
    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: '[email protected]',
         createdAt: '2016-12-01T22:07:34.988Z',
         updatedAt: '2016-12-01T22:07:34.994Z',
         id: 3 } ],
    name: 'test',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:19.723Z',
    updatedAt: '2016-12-01T22:09:41.410Z',
    id: 1 },
  { friends: [],
    name: 'test2',
    email: '[email protected]',
    createdAt: '2016-12-01T22:06:40.808Z',
    updatedAt: '2016-12-01T22:06:40.808Z',
    id: 2 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:09:41.410Z',
         id: 1 } ],
    name: 'test3',
    email: '[email protected]',
    createdAt: '2016-12-01T22:07:34.988Z',
    updatedAt: '2016-12-01T22:07:34.994Z',
    id: 3 },
  { friends: 
     [ { name: 'test',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:19.723Z',
         updatedAt: '2016-12-01T22:09:41.410Z',
         id: 1 },
       { name: 'test2',
         email: '[email protected]',
         createdAt: '2016-12-01T22:06:40.808Z',
         updatedAt: '2016-12-01T22:06:40.808Z',
         id: 2 } ],
    name: 'test4',
    email: '[email protected]',
    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!

Semipermeable answered 1/12, 2016 at 22:22 Comment(0)
L
0

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

Lemuel answered 8/10, 2014 at 4:37 Comment(2)
I really appreciate your reply. When I meant .populate('friends') but it doesn't work :-(Toxophilite
@Toxophilite Take a try: collection: 'User',Lemuel
T
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.

Toxophilite answered 9/10, 2014 at 9:7 Comment(2)
Dominant is used for cross-adapter queries, not for what you describe. sailsjs.com/documentation/concepts/models-and-orm/associations/… It just says where is the helper table going to be createdSemipermeable
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.Toxophilite

© 2022 - 2024 — McMap. All rights reserved.