Sequelize: How to define relationship when models are defined in separate files?

Created on 27 Sep 2015  路  37Comments  路  Source: sequelize/sequelize

Hi

I'm wondering what is the recommended way to define the relationship when the models are defined in separate files.

Assuming that I have a company model,

// models/Company.js
module.exports = function(sequelize, DataTypes) {
  return sequelize.define('Company', {
  }
};

and a project model,

// models/Project.js
module.exports = function(sequelize, DataTypes) {
  return sequelize.define('Project', {
  }
};

Then, I have an index.js that will read and import the above models,

// models/index.js
files.forEach(function(file) {
  sequelize.import(file);
});

// sequelize.models now contains Company and Project
// then define the relationship: a Company has many Projects
sequelize.models.Company.hasMany(sequelize.models.Project);

Is this the right way to define the relationships? I can imagine that there will be a lot of relationships defined in the same file when there are a lot of models.

Most helpful comment

Looks like @mickhansen is living the dream of being an absolute dickhead, congratulations.

All 37 comments

I personally use a relations.js file, some people add an associate method to their models and call that after everything is imported.

Can you please show me the code of your relation.js file?

module.exports = function(models) {
  // setup relations
};

Hi @mickhansen thank you so much for this quick post. Actually I have really got stuck in the same situation. I have my two models in separate files:

company.model.js

module.exports = function(sequelize, DataTypes) {
  var Company = sequelize.define('Company', {
    id: {
      type: DataTypes.INTEGER(3),
      allowNull: false,
      primaryKey: true,
      autoIncrement: true,
      comment: "Primary and auto incremented key of the table"
    },
    companyName: {
      field: "company_name",
      type: DataTypes.STRING(255),
      allowNull: false,
      comment: "Company Name"
    }
},
{
    underscored: true,
    freezeTableName:true,
    tableName:'company',
    classMethods:{
      associate:function(models){
        Company.hasMany(models.Location);
      }
    },
  });
  return Company;
};

location.model.js

['use strict';

module.exports = function(sequelize, DataTypes) {
  var Location = sequelize.define('Location', {
    id: {
      type: DataTypes.INTEGER(5),
      allowNull: false,
      primaryKey: true,
      autoIncrement: true,
      comment: "Primary and auto incremented key of the table"
    },
    locationType: {
      field: "location_type",
      type: DataTypes.ENUM('HEAD_OFFICE','REGIONAL_OFFICE','FRANCHISE'),
      allowNull: false,
      comment: "Type of location"
    }
  },
  {
    timestamps: true,
    underscored: true,
    freezeTableName:true,
    tableName:'location',
    classMethods:{
      associate:function(models){
        Location.belongsTo(models.Company, { foreignKey:'company_id', foreignKeyConstraint:true} );
      }
    }
  });

  return Location;
};

and I have the this final js file index.js

'use strict';

import path from 'path';
import config from '../config/environment';
import Sequelize from 'sequelize';

var sequelizeConnection = new Sequelize(config.sequelize.database, null, null, config.sequelize.options);

var db = {
  Sequelize: Sequelize,
  sequelize: sequelizeConnection
};

// Insert models below
db.Company = db.sequelize.import('../api/company/company.model');
db.Location = db.sequelize.import('../api/location/location.model');
db.User = db.sequelize.import('../api/user/user.model');
db.Thing = db.sequelize.import('../api/thing/thing.model');

module.exports = db;

When I run the following code

db.sequelize.sync({force:true})
  .then(startServer)
  .catch(function(err) {
    console.log('Server failed to start due to error: %s', err);
  });

The tables are created absolutely fine but there is not foreign key collum on location table and nothing regarding foreign key constrain.

Can you please help me to solve it. I will really appreciate your solution. Thanks a ton.

I don't see you calling the .associate method anywhere. Keep in mind it's not a magic built in Sequelize method, it's just a pattern used by some examples.

So what should I do to work it correctly. Do you have any proposed solution. I'm really new to sequelizeJS.

Either call the method or setup associations in a file like i suggested. Try SOMETHING, it doesn't sound like you've tried anything. The code that setups associations needs to be actually called, verify that happens :)

I tried to call it using the following:

var db = {
  Sequelize: Sequelize,
  sequelize: sequelizeConnection
};

// Insert models below
db.Company = db.sequelize.import('../api/company/company.model');
db.Location = db.sequelize.import('../api/location/location.model');
db.User = db.sequelize.import('../api/user/user.model');
db.Thing = db.sequelize.import('../api/thing/thing.model');

Object.keys(db).forEach(function(modelName) {
  if (db[modelName].options.hasOwnProperty('associate')) {
    db[modelName].options.associate(db)
  }
})

module.exports = db;

You are putting associate in classMethods, but looking for it in options - so the function is not called

I also tried it db[modelName].classMethods.associate(db) man but nothing is working :(

classMethods are adding methods to the class, have you tried inspecting the object? Or just calling db[modelName].associate?

@mjangir Please do basic debugging on your own before taking up other peoples time, it's the polite thing to do :)

Basic debugging includes checking if and why a method is or is not called, this is basic javascript, nothing Sequelize specific.

Wow, simply two lines worked like a charm for me.

db.Company.hasMany(db.Location);
db.Location.belongsTo(db.Company);

But I think its not a good practice. Please suggest me to manage all them in separate files whenever you get time.

By the way many many thanks to give your important time.

@mjangir I personally use a single relations.js file as mentioned.

@mjangir Can you please post a code snippet , how you are managing all relationship in relationship.js file.

@joinsunil I am doing something like this

index.js:

const CLASSMETHODS = 'classMethods';
const ASSOCIATE = 'associate';
var sequelize = new Sequelize(cfg.db.database, cfg.db.user, cfg.db.password, cfg.db.options);

fs.readdirSync(__dirname).filter(function (file) {
    return (file.indexOf('.') !== 0) && (file !== 'index.js');
}).forEach(function (file) {
    var model = sequelize['import'](path.join(__dirname, file));
    db[model.name] = model;});
Object.keys(db).forEach(function (modelName) {
    if (CLASSMETHODS in db[modelName].options) {
     if (ASSOCIATE in db[modelName].options[CLASSMETHODS]) {
      db[modelName].options.classMethods.associate(db);
    }}});

Character.js:

module.exports = (sequelize, DataTypes) => {
    const Character = sequelize.define('Character', {
      Id: { type: DataTypes.INTEGER, primaryKey: true, autoincrement: true },
      FirstName: DataTypes.STRING,
      LastName: DataTypes.STRING,
      DoB: DataTypes.DATE,
      createdAt: DataTypes.DATE,
      updatedAt: DataTypes.DATE
    }, {
      classMethods: {
      associate: (models) => {
        Character.belongsTo(models.CharacterDetail, {
          foreignKey: 'fk_detail_id',
          as: 'Detail'})}}});
      return Character;
}

Have you discovered a better approach?

@Infer-On You are correct..

This worked for me

model animal.js

/* jshint indent: 2 */
module.exports = (sequelize, DataTypes) => {
  const animal = sequelize.define('animal', {
      id: {
        type: DataTypes.INTEGER(11),
        allowNull: false,
        primaryKey: true,
        autoIncrement: true
      },
      non_shelter: {
        type: DataTypes.INTEGER(4),
        allowNull: true
      }
    }
  );

  animal.associate = function (models) {
    animal.hasMany(models.lookup_animal_type);
  };

  return animal
};

index.js

"use strict";

var fs        = require("fs");
var path      = require("path");
var Sequelize = require("sequelize");
var env       = process.env.NODE_ENV || "development";
const Op = Sequelize.Op;
var config    = require(path.join(__dirname, '..', 'config', 'config.json'))[env];
if (process.env.DATABASE_URL) {
  var sequelize = new Sequelize(process.env.DATABASE_URL,config);
} else {
  var sequelize = new Sequelize(config.database, config.username, config.password, config);
}
var db = {};

fs
  .readdirSync(__dirname)
  .filter(function(file) {
    return (file.indexOf(".") !== 0) && (file !== "index.js");
  })
  .forEach(function(file) {
    var model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach(function(modelName) {
  if ("associate" in db[modelName]) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.sequelize.sync();
modul
e.exports = db;


importing single model file in sequelize is hell. Thanks for the above approaches.

Looks like @mickhansen is living the dream of being an absolute dickhead, congratulations.

@OmarKRostom Excuse me?

I see no reason not to help others, simple.

You have the credits for creating a very good ORM, yet you destroyed my respect to you with those disrespectful comments.

What a shame !

And not just me, in case you think about the heck you care about my opinion.

Because frankly am no body.

@OmarKRostom So you think a personal attack is okay? Interesting take.

At the time we were probably having to answer 20-30 issues a day, so less important issues got less attention compared to actual bug reports etc. But thanks for your feedback on how to manage FOSS :)

I'm not the creator of Sequelize nor am i an active maintainer.

Edit: Setting up relations never had one official way to do it (google some blog posts on it), so i could only speak from my personal opinion on how to organize association calls.

I am sorry on being rude replying, but I was very furious by your replies.

@OmarKRostom Understandable, no hard feelings - however context means a lot, especially on a 2+ year old thread. I could probably/definitely have been a lot more forthcoming but we probably had this question a few times a day and it took up attention from bugs.

@OmarKRostom You're right, it wasn't just you. For every person who has the audacity to say something about needless rudeness, there are probably a hundred who fume in silence and walk away. Frankly, when the documentation is as lacking as this project's was two years ago, people have no choice but to ask questions repeatedly. It would have been faster to just answer them.

I also lost respect for this developer.

we probably had this question a few times a day and it took up attention from bugs

@mickhansen Perhaps the documentation could be clearer, then. I've used Sequelize for a handful of projects over the years and I still have to spend a couple days fighting simple model associations every time I set it up. Things simply don't work as expected. The example code available does not work in several cases. It's difficult to find anything clear or specific on the topic in the documentation. The documentation makes a lot of assumptions about where the reader is coming from.

I'd be quite happy to improve the documentation, this is open source after all, but I'd have to figure out how to work this package myself first.

It's unfortunate, as Sequelize is definitely one of the better Node.js ORMs available, once you get it running.

Ran into this problem, my approach was just to global require each model file, and spread, loop and associate.

model index.js

// require our model files
const models = {
  ...require('./categoryModel'),
  ...require('./eventModel'),
  ...require('./featureModel'),
  ...require('./gameModel'),
  ...require('./mediaModel'),
  ...require('./milestoneModel'),
  ...require('./projectModel'),
  ...require('./roleModel'),
  ...require('./taskModel'),
  ...require('./teamModel'),
  ...require('./mediaModel'),
  ...require('./userModel')
};

// call associate on each of the models
Object.keys(models).forEach(key => {
  if (models[key] && models[key].associate) {
    models[key].associate(models);
  }
});

module.exports = models;

I cannot find association function or any syntax like "Model.associate = function(models) {" any where in the sequelize docs. Can any one please help me to find details about association function in docs or anywhere.

Looking for the same problem but as always documentation is bas and you have to guess or google a lot

The solution was ver simple when you find it this was mine "models.chatuser" where chatuser is a model accociated 1-1 with
chatmessage.associate = function (models) {
chatmessage.hasOne(models.chatuser, { foreignKey: 'chatuserid' });
};

I tried to call it using the following:

var db = {
  Sequelize: Sequelize,
  sequelize: sequelizeConnection
};

// Insert models below
db.Company = db.sequelize.import('../api/company/company.model');
db.Location = db.sequelize.import('../api/location/location.model');
db.User = db.sequelize.import('../api/user/user.model');
db.Thing = db.sequelize.import('../api/thing/thing.model');

Object.keys(db).forEach(function(modelName) {
  if (db[modelName].options.hasOwnProperty('associate')) {
    db[modelName].options.associate(db)
  }
})

module.exports = db;

Try it this way:

Object.keys(db).forEach(function(modelName) {
  if ('classMethods' in db[modelName].options) {
    db[modelName].options.classMethods.associate(db);
  }
});

Here is a package to help with that:
https://www.npmjs.com/package/sequelize-associate

Was this page helpful?
0 / 5 - 0 ratings