Piepongwong
Piepongwong

Reputation: 85

Many to many self reference in Sequelize in twitter like web-app

I'm trying to build a blog web-application as an excercise to understand ORM and Sequelize in particular. I managed quite easily to form relations between posts and comments and users and posts. No problems there. The horror begins when I try to link users to users as followers and users to users as followeds. I also want it to be possible to follow yourself.

I have tried something like this:

  const User = sequelize.define('user', {
    firstName: {
      type: Sequelize.STRING
    },
    lastName: {
      type: Sequelize.STRING
    },
    email: {
      type: Sequelize.STRING
    },
    password: {
      type: Sequelize.STRING
    },
    activated: {
      type: Sequelize.BOOLEAN
    }
  })
User.belongsToMany(User, {as: "Follower", foreignKey: "FollowerId", through: "Follower_Followeds"})
User.belongsToMany(User, {as: "Followed", foreignKey: "FollowedId", through: "Follower_Followeds"})

And to set the relations:

app.post("/followhandler", function(req, res) {
    let userId = req.session.userId
    let followId = req.body.followId

    User.findById(userId)
    .then( currentUser => {
        User.findById(followId)
        .then( follows => {
            currentUser.addUser(follows)
        })
    })
    .catch(e => console.log(e)) 
})

But the Follower_Followeds table will simply not get updated. I've also tried to skip the many to many implementation of Sequelize by creating a separate table and adding the relations manually:

const FollowerFollowed = sequelize.define('followerFollowed', {
  followedId: {
    type: Sequelize.INTEGER
  },

  followerId: {
    type: Sequelize.INTEGER
  }  
})

//define Users
  const User = sequelize.define('user', {
    firstName: {
      type: Sequelize.STRING
    },
    lastName: {
      type: Sequelize.STRING
    },
    email: {
      type: Sequelize.STRING
    },
    password: {
      type: Sequelize.STRING
    },
    activated: {
      type: Sequelize.BOOLEAN
    }
  })

And the handler:

app.post("/followhandler", function(req, res) {
    let userId = req.session.userId
    let followId = req.body.followId

    FollowerFollowed.create( {
        followerId: userId,
        followedId: followId
    })
    .then( rel => console.log(rel))
    .catch(e => console.log(e)) 
})

But I'm getting the following error in this case:

TypeError: val.replace is not a function at Object.SqlString.escape (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/sql-string.js:61:15) at Object.escape (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/dialects/abstract/query-generator.js:978:22) at Object.insertQuery (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/dialects/abstract/query-generator.js:299:28) at QueryInterface.insert (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/query-interface.js:497:33) at . (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/instance.js:679:56) at tryCatcher (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/util.js:16:23) at Promise._settlePromiseFromHandler (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:510:31) at Promise._settlePromise (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:567:18) at Promise._settlePromise0 (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:612:10) at Promise._settlePromises (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:691:18) at Async._drainQueue (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/async.js:133:16) at Async._drainQueues (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/async.js:143:10) at Immediate.Async.drainQueues (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/async.js:17:14) at runCallback (timers.js:649:20) at tryOnImmediate (timers.js:622:5) at processImmediate [as _immediateCallback] (timers.js:594:5) TypeError: val.replace is not a function at Object.SqlString.escape (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/sql-string.js:61:15) at Object.escape (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/dialects/abstract/query-generator.js:978:22) at Object.insertQuery (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/dialects/abstract/query-generator.js:299:28) at QueryInterface.insert (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/query-interface.js:497:33) at . (/home/piepongwong/Documents/NYCDA/blog/node_modules/sequelize/lib/instance.js:679:56) at tryCatcher (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/util.js:16:23) at Promise._settlePromiseFromHandler (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:510:31) at Promise._settlePromise (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:567:18) at Promise._settlePromise0 (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:612:10) at Promise._settlePromises (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/promise.js:691:18) at Async._drainQueue (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/async.js:133:16) at Async._drainQueues (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/async.js:143:10) at Immediate.Async.drainQueues (/home/piepongwong/Documents/NYCDA/blog/node_modules/bluebird/js/release/async.js:17:14) at runCallback (timers.js:649:20) at tryOnImmediate (timers.js:622:5) at processImmediate [as _immediateCallback] (timers.js:594:5)

Upvotes: 1

Views: 1449

Answers (1)

piotrbienias
piotrbienias

Reputation: 7411

When you create the M:M association you use as: 'Follower' and as: 'Followed' attributes. It influences the way you can add/set/delete associations between users. Method to add followed user to other user is now called addFollowed and would be used in exactly the same way you did it.

User.findById(userId).then( currentUser => {
    User.findById(followId).then( follows => {
        currentUser.addFollowed(follows); // notice the difference here
    });
});

The same occurs if you want to add follower to given user - then you would use addFollower method.

Moreover, in your second solution you have made a mistake while creating the associating Model. In the User model you have defined associations via foreign keys FollowerId and FollowedId, however in the FollowerFolloweds definition you have created followerId and followedId, so those two do not match each other - you need to keep the names consistent.

EDIT

According to the documentation in the belongsToMany relation, the as attribute should be named as plural, so in your case you should name those as: 'Followers' and as: 'Followeds' (the second one seems a little strange). Sequelize singularizes those values itself, however you can define it by yourself using object as: { singular: 'Follower', plural: 'Followers' }

The alias of this association. If you provide a string, it should be plural, and will be singularized using node.inflection. If you want to control the singular version yourself, provide an object with plural and singular keys.

Upvotes: 2

Related Questions