Kai
Kai

Reputation: 43

Sequelize: Problems with nested include / reverse association

I'm pretty new to JS and Sequelize and I'm now facing problems when querying the following structure. Maybe I am totally missing some basic point here. Help is much appreciated.

Versions:

Structure

A flight belongs always to one user. Different users can create comments to one flight.

enter image description here

Goal

When i select a flight by it's ID, i want to include the name of the user and i want to include all comments to that flight with the name of the user which created the comment.

Problem

I'm able to include the comment to the flight. But I'm not able to achive the other goals. When I run:
await Flight.findOne({
      where: { id: flightId },
      include: [
        {
          model: User,
          as: "user",
          attributes: ["id", "name"],
        },
        {
           model: FlightComment,
           as: "comments",
        },
      ],
    });

I will get the Error

SequelizeEagerLoadingError: User is not associated to Flight!

which is understandable. So I tried to add the reverse association to the Flight.

Flight.belongsTo(User)

After that I will get the error

Flight.belongsTo called with something that's not a subclass of Sequelize.Model

When I define a userId column in the FlightComment like:

  userId: {
    type: DataTypes.UUID,
    references: {
      model: User,
      key: "id",
    },
  },

I will get the following error during database synchrinisation

Executing (default): DROP TABLE IF EXISTS "FlightComments" CASCADE;
TypeError: Cannot read property 'replace' of undefined

I read that you need to define all models in one file, but because of the many numbers of different models I want to keep things apart.

I didn't find any advise in the offical documentaion either.

Model-Files

For every model I've created a own file (there will be alot of different models, so better keep things apart).

File for Flight:

const Flight = db.sequelize.define("Flight", {
  //Many beautiful attributes
});

Flight.hasMany(FlightComment, {
  as: "comments",
  foreignKey: {
    name: "flightId",
    allowNull: false,
  },
  onDelete: "CASCADE",
  hooks: true,
});

//Another association

module.exports = Flight;

File for FlightComment:

const FlightComment = db.sequelize.define("FlightComment", {
  id: {
    type: Sequelize.UUID,
    defaultValue: Sequelize.UUIDV4,
    allowNull: false,
    primaryKey: true,
  },

  message: {
    type: DataTypes.STRING,
    allowNull: false,
  },
});

module.exports = FlightComment;

File for User:

const User = db.sequelize.define(
  "User",
  {
    id: {
      type: Sequelize.UUID,
      defaultValue: Sequelize.UUIDV4,
      allowNull: false,
      primaryKey: true,
    },
    name: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true, 
    },
    //Many more attributes
  },
);

User.hasMany(Flight, {
  as: "flights",
  foreignKey: {
    name: "userId",
    allowNull: false,
  },
});

User.hasMany(FlightComment, {
  as: "comments",
  foreignKey: {
    name: "userId",
    allowNull: false,
  },
  onDelete: "CASCADE",
  hooks: true,
});

module.exports = User;

Upvotes: 0

Views: 915

Answers (2)

r9119
r9119

Reputation: 606

You can initialise your db and its associations programmatically (with a file in the same folder as models) like:

'use strict'
 
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const config = {
  "username": "root",
  "password": "YOUR ROOT PASSWORD HERE",
  "database": "YOUR DATABASE NAME HERE",
  "dialect": "postgres"
}
const db = {}
 
let sequelize = new Sequelize(config.database, config.username, config.password, config);
 
fs.readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0)
    && (file !== basename)
    && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });
 
Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});
 
db.sequelize = sequelize;
db.Sequelize = Sequelize;
 
module.exports = db;

With this you need to declare your model files like this:

module.exports = Flight = (sequelize, DataTypes) => {
    const Flight = sequelize.define('Flight', {
        //Many beautiful attributes
    })

    Flight.associate = models => {
        Flight.hasMany(models.FlightComment, { 
            as: 'comments',
            foreignKey: {
                name: "flightId",
                allowNull: false,
            },
            onDelete: "CASCADE",
            hooks: true
        })
        Flight.belongsTo(models.User, {
            as: 'flight',
            foreignKey: {
                name: "flightId",
                allowNull: false,
            }
        })
    }

    return Flight
} 

Upvotes: 1

Kai
Kai

Reputation: 43

At the end I defined all models, which are depending on another, within one file. This is not the solution I was searching for but it works...

Upvotes: 1

Related Questions