简体   繁体   中英

How to do a Self-Referencing Many-to-Many Relationship in Bookshelf

I'm trying to work out the most expressively clear and "correct" way in Bookshelf.Js of setting up a many-to-many relationship for a User model to build-out the standard followers/followed relationship that exists on many social applications. The documentation in Bookshelf is throwing me for a loop, though.

I can see two possible approaches: 1) Using belongsToMany() alone, or 2) Using belongsToMany() with a throught() . Neither have worked for me, but let me show you first what I'm doing for (1). I start with this Model setup:

const User = bookshelf.Model.extend({
  tableName: 'users',
  initialize: function() {
    this.on('creating', this.encryptPassword);
  },
  hasTimestamps: true,
  posts: function() {
    return this.hasMany(Posts, 'author');
  },
  comments: function() {
    return this.hasMany(Comments);
  },
  following: function() {
    return this.belongsToMany(UserFollow, 'users_users', 'follower_id', 'user_id');
  },
  followers: function() {
    return this.belongsToMany(UserFollow, 'users_users', 'user_id', 'follower_id');
  },
});

And to match that I have a knex migration that adds a users_users table like so:

exports.up = function(knex, Promise) {
  return knex.schema.createTable('users_users', (tbl) => {
    tbl.integer('user_id').references('users.id');
    tbl.integer('follower_id').references('users.id');
    tbl.unique(['user_id', 'follower_id']);  
      // This b/c I read a few places that Bookshelf might require a composite key.
  });
};

exports.down = function(knex, Promise) {
  return knex.schema.dropTable('users_users');
};

Then I have the following test:

it('User model can record a follower', (done) => {
    const saveUsr = (data) => {
      return User.forge().save(data, {transacting: transaction});
    };
    Promise.all([saveUsr(mockUser), saveUsr(anotherMockUser)])
      .then((results) => {
        let usr1 = results[0];
        let usr2 = results[1];
        return usr2.followers().attach(usr1.id, {transacting: transaction});
      })
      .then((usrWithFollower) => {
        return usrWithFollower.fetch({
          withRelated: ['followers'],
          transacting: transaction
        });
      })
      .then((usr) => {
        console.log(JSON.stringify(usr));
        console.log('followers: ', usr.get('followers'));
        done();
      })
      .catch((err) => { done(err); });
   });

I'm not really testing much here as I've not been able to get the thing, working, but the result of JSON.stringify(user) in the last promise then callback is the following:

[
   {
      "id":1,
      "name":"Sally Low",
      "username":"sally",
      "email":"sally@example.org",
      "created_at":"2016-06-05T15:24:41.835Z",
      "updated_at":"2016-06-05T15:24:41.835Z",
      "password":"$2a$10$613tL/baML9obsRN4Yg7MeU/2RiH7oK/nFUwfpQ1GYuA15VUFrFZu",
      "followers": [],
      "_pivot_user_id":2,
      "_pivot_follower_id":1
   }
]

As you can see, it looks as if some sort of relation has been setup here given that there are these _pivot_ props, but what they are is not clear to me, and I'm not sure why the followers field is coming back empty.

If anyone can set me straight here or illuminate the situation in any way, I'd be much obliged.

I suppose those UserFollow models to be User aliases. This is not needed.

Try adding the Registry Plugin and write your User model as:

const User = bookshelf.Model.extend({
  tableName: 'users',
  // ...
  following: function() {
    return this.belongsToMany('User', 'users_users', 'follower_id', 'user_id');
  },
  followers: function() {
    return this.belongsToMany('User', 'users_users', 'user_id', 'follower_id');
  },
});
bookshelf.model('User', User);

The attach() code may use a model directly, no need to use the id :

usr2.followers().attach(usr1);

I made a minimal version of your schema and tried:

new User({name: 'Bia'}).
  fetch({withRelated: 'followers'}).
  then((m) => {console.log(m.toJSON());});

That gave me back:

{ name: 'Bia',
   id: 3,
   followers: 
    [ { id: 1, name: 'Ana', _pivot_user_id: 3, _pivot_follower_id: 1 },
      { id: 2, name: 'Mia', _pivot_user_id: 3, _pivot_follower_id: 2 } ] }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM