sequelize icon indicating copy to clipboard operation
sequelize copied to clipboard

belongsToMany Association on the same model with a different foreign key and alias fails

Open callmekatootie opened this issue 7 years ago • 24 comments

What you are doing?

I have defined my association on the User model as follows

    User.belongsToMany(User, {
      through: models.Following,
      as: 'Follower',
      foreignKey: 'following_id',
    });

    User.belongsToMany(User, {
      through: models.Following,
      as: 'Following',
      foreignKey: 'follower_id',
    });

What do you expect to happen?

I did this so that I could then call user.GetFollower() and user.GetFollowing() functions. This worked fine with sequelize v3

What is actually happening?

With sequelize v4, I now get the error SequelizeAssociationError: You have used the alias Following in two separate associations. Aliased associations must have unique aliases.

If I remove the second User.belongsToMany, it solves the problem but I am no longer able to use user.getFollowing() which returns as undefined.

Dialect: mysql Database version: 5.7 Sequelize version: 4.8

P.S. I did check https://github.com/sequelize/sequelize/issues/7775. But it is not applicable to my case. I do not have the Model.associate(models) being called multiple times. I have it called only once - in the index.js file that sequelize-cli generated.

callmekatootie avatar Sep 06 '17 16:09 callmekatootie

Some more hints:

If I replace the through with a string 'Following' instead of an existing model, then it works fine. I don't get the error. I have defined the models.Following as :

function FollowingModel(sequelize, DataTypes) {
  const Following = sequelize.define('Following', {
    follower_id: {
      allowNull: false,
      primaryKey: true,
      type: DataTypes.INTEGER,
      references: {
        model: 'Users',
        key: 'id',
      },
    },
    following_id: {
      allowNull: false,
      primaryKey: true,
      type: DataTypes.INTEGER,
      references: {
        model: 'Users',
        key: 'id',
      },
    },
  }, {
    timestamps: true,
    underscored: true,
    updatedAt: false,
  });

  return Following;
}

module.exports = FollowingModel;

callmekatootie avatar Sep 07 '17 08:09 callmekatootie

This might not be your problem, but try setting the alias singular and plural form of your association: User.belongsToMany(User, { as: { singular: 'follows', plural: 'following; }})

Since Sequelize uses the inflection library to convert aliases to, in this case, their singular form, read about association naming strategy

And the singular form of Following is Following this might be your problem.

inflection.singularize( 'following');
"following"

holmberd avatar Sep 08 '17 15:09 holmberd

I have the same problems with the following association:

Users.belongsToMany(models.Places, { through: 'Checkins', as: 'Checkins' })
Places.belongsToMany(models.Users, { through: 'Checkins', as: 'Checkins' })

I tried removing the alias on one-or-the-other and it still fails.
I ended up removing the aliases but it makes the code readability confusing.
Subscribing.

ghost avatar Oct 31 '17 10:10 ghost

I have issues here as well:

 models.User.belongsToMany(models.Company, { as: 'JoinCompanies', through: 'CompanyJoin', onDelete: 'CASCADE' });
 models.Company.belongsToMany(models.User, { as: 'JoinUsers', through: 'CompanyJoin', onDelete: 'CASCADE' });

 SequelizeAssociationError: You have used the alias CompanyJoin in two separate associations. 
 Aliased associations must have unique aliases.

ahlwong avatar Nov 18 '17 06:11 ahlwong

Seems that association declarations are completely changed now, and instance and class methods have been removed in 4.x as I understand it. I guess backward compatibility is a thing of the past.

chornbe avatar Dec 05 '17 20:12 chornbe

@chornbe to sequelize maintainers defence, they do a good use of semantic versioning, not sure what's your point.

ghost avatar Dec 15 '17 09:12 ghost

@chornbe @kytwb @callmekatootie Please what is the state of this issue? Where you able to resolve this in anyway? I am having the same issue.

Song.belongsToMany(models.Term, { through: models.SongCategory, as: 'SongCategory', foreignKey: 'songId' })
Term.belongsToMany(models.Song, {through: models.SongCategory, as: 'Category', foreignKey: 'categoryId'})

smithaitufe avatar Dec 23 '17 16:12 smithaitufe

@smithaitufe iirc, I ended up not using Following or rather name of the model itself for the through property and instead using Followings or something not same as the model name which then worked.

callmekatootie avatar Dec 23 '17 17:12 callmekatootie

@callmekatootie Thanks. It worked when I changed from as: SongCategory to as: SongCategories even though it is not want I am comfortable with

smithaitufe avatar Dec 23 '17 17:12 smithaitufe

The workaround works for me too for now, thanks.

ghost avatar Jan 26 '18 06:01 ghost

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If this is still an issue, just leave a comment 🙂

stale[bot] avatar Apr 26 '18 06:04 stale[bot]

I have the same problem.

User.hasMany(Task, {
	as: 'AssignedTasks',
	foreignKey: 'assigned**To**UserId'
User.hasMany(Task, {
	as: 'AssignedTasks',
	foreignKey: 'assigned**By**UserId'

mattwerthva avatar Jul 02 '18 11:07 mattwerthva

I got the same error when I accidentally added one model twice. hope it could help someone

CheStas avatar Jul 23 '18 13:07 CheStas

That... that is not good. The longer I use it, the more I think I'm just going to write my queries as needed, and start moving away from Sequelize as a data object layer. Which stinks; in its basic form, a couple versions old, it was a great way to get basic, simple-data-model with basic relations working quickly and clearly. There's just too much finicky tweaking the last year or two for my liking. :(

Chris Hornberger [email protected]

On Thu, Jul 26, 2018 at 9:38 AM, Bogdan Polovko [email protected] wrote:

I'm trying to update to Sequelize 4. In my case I have two models using the same alias. That's how front-end expects it to be:

Clinic.belongsTo(Location, { as: 'location', foreignKey: 'locationId' });Location.hasOne(Clinic, { as: 'location', foreignKey: 'locationId' }); Company.belongsTo(Location, { as: 'location', foreignKey: 'locationId' });Location.hasOne(Company, { as: 'location', foreignKey: 'locationId' });

Why should this throw an error, what's wrong with using two same aliases on two different models? Is there a way to disable it? Otherwise all our front-end applications will have to be updated, which is insane amount of work.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sequelize/sequelize/issues/8263#issuecomment-408100247, or mute the thread https://github.com/notifications/unsubscribe-auth/AHJ5ebUajwAF7LgN1bOZnTC7woS3_VWSks5uKcZPgaJpZM4POm7F .

chornbe avatar Jul 26 '18 13:07 chornbe

I've got the same issue, it really doesn't make any sense, and now I'm stuck using the term 'Availabilities' on my model which is a terrible name.

antony avatar Jul 27 '18 10:07 antony

I experienced this error message myself when I was trying a three way binding with belongsToMany.

It turned out that I had one modal definition where I was using one hasMany instead of belongsToMany 🙈

Gogoro avatar Jan 10 '19 08:01 Gogoro

same problem

but1head avatar Mar 22 '19 10:03 but1head

In version 4.37.7, I ran into this with the following structure:

Prairie.hasMany(PrairieFlowerSpecies, { foreignKey: 'prairie_id', sourceKey: 'prairie_id' });
Prairie.belongsToMany(FlowerSpecies, {
  through: PrairieFlowerSpecies,
  as: 'flower_species',
  foreignKey: 'flower_species_id',
  otherKey: 'prairie_id',
});

I took a look at the source code and it looks like the lib is temporarily? defining a relationship between Prairie and PrairieFlowerSpecies in the process of adding the belongsToMany relationship, even though one already exists. Rearranging it eliminated the error:

Prairie.belongsToMany(FlowerSpecies, {
  through: PrairieFlowerSpecies,
  as: 'flower_species',
  foreignKey: 'flower_species_id',
  otherKey: 'prairie_id',
});
Prairie.hasMany(PrairieFlowerSpecies, { foreignKey: 'prairie_id', sourceKey: 'prairie_id' });

This doesn't look like it would fix OP's problem but it's weird and I hope it helps somebody out. Thanks for this thread folks.

kcsoderstrom avatar Jul 24 '19 17:07 kcsoderstrom

I have reproduced the issue with the following super-minimal code (by completing OP's code):

const Sequelize = require("sequelize");
const sequelize = new Sequelize("sqlite::memory:");

const User = sequelize.define('User', {});
const Following = sequelize.define('Following', {});

User.belongsToMany(User, {
    through: Following,
    as: 'Follower',
    foreignKey: 'following_id',
});
User.belongsToMany(User, {
    through: Following,
    as: 'Following',
    foreignKey: 'follower_id',
});

Result:

SequelizeAssociationError: You have used the alias Following in two separate associations. Aliased associations must have unique aliases.

papb avatar Jul 24 '19 20:07 papb

in my case @kcsoderstrom solution fixed my issue

zaidfadhil avatar Sep 17 '19 09:09 zaidfadhil

one of my biggest problems using ES6 to declare the associations it was because the static method is never called by himself so i has to call it before start doing requests

const schema = new ApolloServer({
  typeDefs,
  resolvers,
  playground: {
    endpoint: "/graphql"
  },
  context: {
    CostPostgreSql: CostPostgreSql.init(sequelize),
    PricePostgreSql: PricePostgreSql.init(sequelize),
    ProductPostgreSql: ProductPostgreSql.init(sequelize)
  }
});
ProductPostgreSql.associate(sequelize);
PricePostgreSql.associate(sequelize);
CostPostgreSql.associate(sequelize);

const app = express();
schema.applyMiddleware({ app });

app.listen(3000, () => {
  console.log("Live on http://localhost:3000/graphql");
});

i declare my relations as follows in the products model, the id of each product is inside of each cost and price element so i use belongsTo when the id is going to be on the source and hasMany when the id is going to be on the target

static associate({ models }) {
    this.belongsTo(models.Unit, { foreignKey: "id" });
    this.belongsTo(models.Provider, { foreignKey: "id" });
    this.hasMany(models.Cost, { foreignKey: "product" });
    this.hasMany(models.Price, { foreignKey: "product" });
  }

and on the costs and prices models i declare something like

  static associate({ models }) {
    this.belongsTo(models.Product, { foreignKey: "id" });
  }

to the moment of make a query i did something like

import { CostPostgreSql } from "../models";
import { PricePostgreSql } from "../models";
db
    .findAll({
      where: { id },
      include: [
        {
          model: CostPostgreSql
        },
        {
          model: PricePostgreSql
        }
      ]
    })
    .then(response => {
      console.log("Exito");
      console.log(JSON.stringify(response));
      return response;
    })
    .catch(err => {
      console.log("fracaso");
      console.log(err);
      return err;
    });

And everything is going well

cubo1123 avatar Nov 11 '19 07:11 cubo1123

All my associations are PascalCase. So I have a simple table "user" with role_id (foreignKey) And I have table "role". So when create a user it works fine. But when I query it search for foreignKey name like RoleId instead of role_id.

module.exports = (sequelize, DataTypes) => { const User = sequelize.define('User', { id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4, allowNull: false, autoIncrement: false, }, email: DataTypes.STRING, password: DataTypes.STRING, RoleId: DataTypes.INTEGER, name: DataTypes.STRING, MerchantId: DataTypes.STRING }, { tableName: 'user' }); User.associate = function (models) { User.belongsTo(models.Role) User.belongsTo(models.Merchant) }; return User; };

module.exports = (sequelize, DataTypes) => { const Role = sequelize.define('Role', { name: DataTypes.STRING }, { tableName: "role" }); Role.associate = function (models) { // associations can be defined here Role.hasMany(models.User) }; return Role; }

Any solution to keep all name lower_case_underscore ?

nicks78 avatar Mar 22 '20 15:03 nicks78

I have reproduced the issue with the following super-minimal code (by completing OP's code):

const Sequelize = require("sequelize");
const sequelize = new Sequelize("sqlite::memory:");

const User = sequelize.define('User', {});
const Following = sequelize.define('Following', {});

User.belongsToMany(User, {
    through: Following,
    as: 'Follower',
    foreignKey: 'following_id',
});
User.belongsToMany(User, {
    through: Following,
    as: 'Following',
    foreignKey: 'follower_id',
});

Result:

SequelizeAssociationError: You have used the alias Following in two separate associations. Aliased associations must have unique aliases.

@papb I just changed the name of alias & the issue was gone. I am just guessing that it can be due to the there already being model's name - Following and therefore sequelize might be wanting unique(all model names + all association alias names) for not having problems when guessing in include. 💡 Which in brief means give unique names wherever you are providing names on your own.

Below code does not give error.

const Sequelize = require("sequelize");
const sequelize = new Sequelize("sqlite::memory:");

const User = sequelize.define('User', {});
const Following = sequelize.define('Following', {}); // <--- 'Following' already used here

User.belongsToMany(User, {
    through: Following,
    as: 'FollowerUsers',  // <--- unique alias name
    foreignKey: 'following_id',
});
User.belongsToMany(User, {
    through: Following,
    as: 'FollowingUsers', // <--- unique alias name
    foreignKey: 'follower_id',
});

abhi5658 avatar Feb 22 '21 15:02 abhi5658

Here's a full minimal runnable example with some tests: https://stackoverflow.com/questions/27065154/how-to-get-all-children-or-parents-in-a-many-to-many-association-if-one-model-re/72951602#72951602 In this example I have a user follows use relationship, and I show how to get both:

  • users followed by a user
  • users that follow a user

The key hard step is defining the many to many both ways at:

User.belongsToMany(User, { through: 'UserFollowUser', as: 'Follows', foreignKey: 'UserId' });
User.belongsToMany(User, { through: 'UserFollowUser', as: 'Followed', foreignKey: 'FollowId' });

main.js

const assert = require('assert')
const path = require('path')
const { DataTypes, Sequelize } = require('sequelize')
let sequelize
if (process.argv[2] === 'p') {
  sequelize = new Sequelize('tmp', undefined, undefined, {
    dialect: 'postgres',
    host: '/var/run/postgresql',
  })
} else {
  sequelize = new Sequelize({
    dialect: 'sqlite',
    storage: 'tmp.sqlite',
  })
}
function assertEqual(rows, rowsExpect) {
  assert.strictEqual(rows.length, rowsExpect.length)
  for (let i = 0; i < rows.length; i++) {
    let row = rows[i]
    let rowExpect = rowsExpect[i]
    for (let key in rowExpect) {
      assert.strictEqual(row[key], rowExpect[key])
    }
  }
}
;(async () => {

// Create the tables.
const User = sequelize.define('User', {
  username: { type: DataTypes.STRING },
}, {});
User.belongsToMany(User, { through: 'UserFollowUser', as: 'Follows', foreignKey: 'UserId' });
// This is ony needed for the function followed. "foreignKey" could be removed otherwise.
User.belongsToMany(User, { through: 'UserFollowUser', as: 'Followed', foreignKey: 'FollowId' });
await sequelize.sync({ force: true });

// Create some users.

const user0 = await User.create({ username: 'user0' })
const user1 = await User.create({ username: 'user1' })
const user2 = await User.create({ username: 'user2' })
const user3 = await User.create({ username: 'user3' })

// Make user0 follow user1 and user2
await user0.addFollows([user1, user2])
// Make user2 and user3 follow user0
await user2.addFollow(user0)
await user3.addFollow(user0)

let rows

// Get users followed by an user by username.
async function followed(username, opts={}) {
  return User.findAll(Object.assign({
    include: [{
      model: User,
      as: 'Followed',
      attributes: [],
      through: { attributes: [] },
      where: { username },
    }],
    // Required for limit to work.
    subQuery: false,
    order: [['username', 'ASC']]
  }, opts))
}
rows = await followed('user0')
assertEqual(rows, [
  { username: 'user1'},
  { username: 'user2'},
])
rows = await followed('user0', { limit: 1 })
assertEqual(rows, [
  { username: 'user1'},
])
rows = await followed('user0', { order: [['username', 'DESC']] })
assertEqual(rows, [
  { username: 'user2'},
  { username: 'user1'},
])

// Now the inverse: find users that follow a given user by username.
async function following(username, opts={}) {
  return User.findAll(
    Object.assign({
      include: [{
        model: User,
        as: 'Follows',
        where: { username },
        attributes: [],
        through: { attributes: [] }
      }],
      // Required for limit to work.
      subQuery: false,
      order: [['username', 'ASC']],
    }, opts)
  )
}
assertEqual(await following('user0'), [
  { username: 'user2' },
  { username: 'user3' },
])
assertEqual(await following('user0', { order: [['username', 'DESC']] }), [
  { username: 'user3' },
  { username: 'user2' },
])
assertEqual(await following('user0', { limit: 1 }), [
  { username: 'user2' },
])
assertEqual(await following('user0', { limit: 1, order: [['username', 'DESC']] }), [
  { username: 'user3' },
])
assertEqual(await following('user1'), [
  { username: 'user0' },
])
assertEqual(await following('user2'), [
  { username: 'user0' },
])
assertEqual(await following('user3'), [])

})().finally(() => { return sequelize.close() });

package.json

{
  "name": "tmp",
  "private": true,
  "version": "1.0.0",
  "dependencies": {
    "pg": "8.5.1",
    "pg-hstore": "2.3.3",
    "sequelize": "6.14.0",
    "sql-formatter": "4.0.2",
    "sqlite3": "5.0.2"
  }
}

GitHub upstream.

Tested on Ubuntu 22.04, PostgreSQL 14.3.

cirosantilli avatar Jul 12 '22 11:07 cirosantilli

I recently encountered a similar problem with the call as follows:

    Transaction.belongsTo(TransactionMode, {
      foreignKey: "transaction_mode_id",
      as: 'transaction_mode'
    });
    Transaction.belongsTo(Account, {
      foreignKey: "initiator_account_id",
      as: 'transaction_initiator'
    });
    Transaction.belongsTo(Account, {
      foreignKey: "counterparty_account_id",
      as: 'transaction_counterparty'
    });
    Transaction.belongsTo(Account, {
      foreignKey: "shipping_fee_paid_by_account_id",
      as: 'shipping_fee_payer'
    });

    Transaction.belongsTo(Account, {
      foreignKey: "escrow_fee_paid_by_account_id",
      as: 'escrow_fee_payer'
    });

    Transaction.belongsTo(TransactionStatus, {
      foreignKey: "finished_transaction_status_id",
      as: 'finished_transaction_status'
    });

    Transaction.belongsTo(ShippingMethod, {
      foreignKey: "shipping_method_id",
      as: 'transaction_shipping_method'
    });

    Transaction.belongsTo(ShippingMethod, {
      foreignKey: "inspection_items_refused_shipping_method_id",
      as: 'return_shipping_method'
    });

    const transaction = await Transaction.findOne({
      where: {
        uuid: transactionUuid
      },
      include: ['transaction_mode', 'transaction_initiator', 'finished_transaction_status', 'transaction_shipping_method',
        'transaction_counterparty', 'shipping_fee_payer',
        'return_shipping_method', 'escrow_fee_payer']
    });

The call produces the following error: 'You have used the alias transaction_mode in two separate associations. Aliased associations must have unique aliases.' After digging into the model at https://github.com/nadrane/sequelize/blob/c41f51651b82d7c3f1e70c6a23b59fa27c0b1034/lib/model.js#L2792C28-L2792C40, I found a simple way to resolve the error: by resetting the associations for the given source, as shown below:

Transaction.associations = new Array;

The full, working call is:

    Transaction.associations = new Array;
    Transaction.belongsTo(TransactionMode, {
      foreignKey: "transaction_mode_id",
      as: 'transaction_mode'
    });
    Transaction.belongsTo(Account, {
      foreignKey: "initiator_account_id",
      as: 'transaction_initiator'
    });
    Transaction.belongsTo(Account, {
      foreignKey: "counterparty_account_id",
      as: 'transaction_counterparty'
    });
    Transaction.belongsTo(Account, {
      foreignKey: "shipping_fee_paid_by_account_id",
      as: 'shipping_fee_payer'
    });

    Transaction.belongsTo(Account, {
      foreignKey: "escrow_fee_paid_by_account_id",
      as: 'escrow_fee_payer'
    });

    Transaction.belongsTo(TransactionStatus, {
      foreignKey: "finished_transaction_status_id",
      as: 'finished_transaction_status'
    });

    Transaction.belongsTo(ShippingMethod, {
      foreignKey: "shipping_method_id",
      as: 'transaction_shipping_method'
    });

    Transaction.belongsTo(ShippingMethod, {
      foreignKey: "inspection_items_refused_shipping_method_id",
      as: 'return_shipping_method'
    });

    const transaction = await Transaction.findOne({
      where: {
        uuid: transactionUuid
      },
      include: ['transaction_mode', 'transaction_initiator', 'finished_transaction_status', 'transaction_shipping_method',
        'transaction_counterparty', 'shipping_fee_payer',
        'return_shipping_method', 'escrow_fee_payer']
    });

Enjoy! :-)

itma avatar Sep 18 '23 18:09 itma