Bagus Santoso
Bagus Santoso

Reputation: 481

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

I'm having error "...called with something that's not a subclass of Sequelize.Model" when I add association of Sequelize in my model it called error that what I call is not Sequelize Model

E:...\Projects\WebApps\hr1\hr1\node_modules\sequelize\lib\associations\mixin.js:81
      throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not a subclass of Sequelize.Model');
      ^

Error: user_employee_tm.class BelongsTo extends Association {
  constructor(source, target, options) {
    super(source, target, options);

    this.associationType = 'BelongsTo';
    this.isSingleAssociation = true;
    this.foreignKeyAttribute = {};

    if (this.as) {
      this.isAliased = true;
      this.options.name = {
        singular: this.as
      };
    } else {
      this.as = this.target.options.name.singular;
      this.options.name = this.target.options.name;
    }

    if (_.isObject(this.options.foreignKey)) {
      this.foreignKeyAttribute = this.options.foreignKey;
      this.foreignKey = this.foreignKeyAttribute.name || this.foreignKeyAttribute.fieldName;
    } else if (this.options.foreignKey) {
      this.foreignKey = this.options.foreignKey;
    }

    if (!this.foreignKey) {
      this.foreignKey = Utils.camelizeIf(
        [
          Utils.underscoredIf(this.as, this.source.options.underscored),
          this.target.primaryKeyAttribute
        ].join('_'),
        !this.source.options.underscored
      );
    }

    this.identifier = this.foreignKey;

    if (this.source.rawAttributes[this.identifier]) {
      this.identifierField = this.source.rawAttributes[this.identifier].field || this.identifier;
    }

    this.targetKey = this.options.targetKey || this.target.primaryKeyAttribute;
    this.targetKeyField = this.target.rawAttributes[this.targetKey].field || this.targetKey;
    this.targetKeyIsPrimary = this.targetKey === this.target.primaryKeyAttribute;

    this.targetIdentifier = this.targetKey;
    this.associationAccessor = this.as;
    this.options.useHooks = options.useHooks;

    // Get singular name, trying to uppercase the first letter, unless the model forbids it
    const singular = Utils.uppercaseFirst(this.options.name.singular);

    this.accessors = {
      get: 'get' + singular,
      set: 'set' + singular,
      create: 'create' + singular
    };
  }

  // the id is in the source table
  injectAttributes() {
    const newAttributes = {};

    newAttributes[this.foreignKey] = _.defaults({}, this.foreignKeyAttribute, {
      type: this.options.keyType || this.target.rawAttributes[this.targetKey].type,
      allowNull: true
    });

    if (this.options.constraints !== false) {
      const source = this.source.rawAttributes[this.foreignKey] || newAttributes[this.foreignKey];
      this.options.onDelete = this.options.onDelete || (source.allowNull ? 'SET NULL' : 'NO ACTION');
      this.options.onUpdate = this.options.onUpdate || 'CASCADE';
    }

    Helpers.addForeignKeyConstraints(newAttributes[this.foreignKey], this.target, this.source, this.options, this.targetKeyField);
    Utils.mergeDefaults(this.source.rawAttributes, newAttributes);

    this.identifierField = this.source.rawAttributes[this.foreignKey].field || this.foreignKey;

    this.source.refreshAttributes();

    Helpers.checkNamingCollision(this);

    return this;
  }

  mixin(obj) {
    const methods = ['get', 'set', 'create'];

    Helpers.mixinMethods(this, obj, methods);
  }

  /**
   * Get the associated instance.
   *
   * @param {Object} [options]
   * @param {String|Boolean} [options.scope] Apply a scope on the related model, or remove its default scope by passing false.
   * @param {String} [options.schema] Apply a schema on the related model
   * @see {@link Model.findOne} for a full explanation of options
   * @return {Promise<Model>}
   */
  get(instances, options) {
    const association = this;
    const where = {};
    let Target = association.target;
    let instance;

    options = Utils.cloneDeep(options);

    if (options.hasOwnProperty('scope')) {
      if (!options.scope) {
        Target = Target.unscoped();
      } else {
        Target = Target.scope(options.scope);
      }
    }

    if (options.hasOwnProperty('schema')) {
      Target = Target.schema(options.schema, options.schemaDelimiter);
    }

    if (!Array.isArray(instances)) {
      instance = instances;
      instances = undefined;
    }

    if (instances) {
      where[association.targetKey] = {
        [Op.in]: instances.map(instance => instance.get(association.foreignKey))
      };
    } else {
      if (association.targetKeyIsPrimary && !options.where) {
        return Target.findByPk(instance.get(association.foreignKey), options);
      } else {
        where[association.targetKey] = instance.get(association.foreignKey);
        options.limit = null;
      }
    }

    options.where = options.where ?
      {[Op.and]: [where, options.where]} :
      where;

    if (instances) {
      return Target.findAll(options).then(results => {
        const result = {};
        for (const instance of instances) {
          result[instance.get(association.foreignKey, {raw: true})] = null;
        }

        for (const instance of results) {
          result[instance.get(association.targetKey, {raw: true})] = instance;
        }

        return result;
      });
    }

    return Target.findOne(options);
  }

  /**
   * Set the associated model.
   *
   * @param {Model|String|Number} [newAssociation] An persisted instance or the primary key of an instance to associate with this. Pass `null` or `undefined` to remove the association.
   * @param {Object} [options] Options passed to `this.save`
   * @param {Boolean} [options.save=true] Skip saving this after setting the foreign key if false.
   * @return {Promise}
   */
  set(sourceInstance, associatedInstance, options) {
    const association = this;

    options = options || {};

    let value = associatedInstance;
    if (associatedInstance instanceof association.target) {
      value = associatedInstance[association.targetKey];
    }

    sourceInstance.set(association.foreignKey, value);

    if (options.save === false) return;

    options = _.extend({
      fields: [association.foreignKey],
      allowNull: [association.foreignKey],
      association: true
    }, options);

    // passes the changed field to save, so only that field get updated.
    return sourceInstance.save(options);
  }

  /**
   * Create a new instance of the associated model and associate it with this.
   *
   * @param {Object} [values]
   * @param {Object} [options] Options passed to `target.create` and setAssociation.
   * @see {@link Model#create}  for a full explanation of options
   * @return {Promise}
   */
  create(sourceInstance, values, fieldsOrOptions) {
    const association = this;

    const options = {};

    if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
      options.transaction = fieldsOrOptions.transaction;
    }
    options.logging = (fieldsOrOptions || {}).logging;

    return association.target.create(values, fieldsOrOptions).then(newAssociatedObject =>
      sourceInstance[association.accessors.set](newAssociatedObject, options)
    );
  }
} called with something that's not a subclass of Sequelize.Model
    at Function.<anonymous> (E:...\Projects\WebApps\hr1\hr1\node_modules\sequelize\lib\associations\mixin.js:81:13)
    at Object.<anonymous> (E:...\Projects\WebApps\hr1\hr1\models\user_employee.js:22:14)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (E:...\Projects\WebApps\hr1\hr1\models\user.js:4:26)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
    at Function.Module._load (internal/modules/cjs/loader.js:529:3)
    at Module.require (internal/modules/cjs/loader.js:636:17)
    at require (internal/modules/cjs/helpers.js:20:18)
    at Object.<anonymous> (E:...\Projects\WebApps\hr1\hr1\routes\index.js:4:12)
    at Module._compile (internal/modules/cjs/loader.js:688:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
    at Module.load (internal/modules/cjs/loader.js:598:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:537:12)

Here's my Code for

model/User.js

var bcrypt =  require('bcrypt');
const sequelize = require('../config/connectionDatabase')
var Sequelize = require('sequelize');
const UserEmployee = require('../models/user_employee');

var User = sequelize.define('user_tm', {
    NameFirst: {
        type: Sequelize.STRING
    },
    NameLast: {
        type: Sequelize.STRING
    },
    username: {
        type: Sequelize.STRING,
        unique: true,
        allowNull: false
    },
    password: {
        type: Sequelize.STRING,
        allowNull: false
    }
}, {
    hooks: {
    beforeCreate: (user) => {
        const salt = bcrypt.genSaltSync();
        user.password = bcrypt.hashSync(user.password, salt);
    }
    },
    instanceMethods: {
    validPassword: function(password) {
        return bcrypt.compareSync(password, this.password);
    }
    }    
});

User.hasOne(UserEmployee, {foreignKey: 'UserID', as: 'User'});
User.prototype.validPassword = function (password) {
    return bcrypt.compareSync(password, this.password);
};
module.exports = User;

model/user_employee.js

const sequelize = require('../config/connectionDatabase');
var Sequelize = require('sequelize');
const User = require('../models/user');

var UserEmployee = sequelize.define('user_employee_tm', {
    DateJoin: {
        type: Sequelize.DATE
    },
    UserID: {
        type: Sequelize.INTEGER,
        references: {
            model: User,
            key: "ID"
        }
    },
    CompanyID: {
        type: Sequelize.INTEGER
    }
});

// UserEmployee.hasOne(User, {as: 'User', foreignKey: 'UserID'});  
UserEmployee.belongsTo(User , {foreignKey: 'ID', as: 'Employee'});
module.exports = UserEmployee;

is there something I missed of? I've try to use this url https://dreamdevourer.com/example-of-sequelize-associations-in-feathersjs/

for adding assosicate along with model, but still having the same problem.

Much thanks for your help

Upvotes: 26

Views: 73036

Answers (13)

Adesayo Adesokan
Adesayo Adesokan

Reputation: 1

This is for those recently having this issue, might not be perfect code, but it works.

I am quite new to sequelize and all, so I did this. I have multiple files for the schema and I am sucker for structured code and trying to learn the clean code model, emphasis on "trying", so this is what I could come up with, it stopped giving the error and quite works as expected, excited to share with everyone.

const { DataTypes } = require('sequelize');
const ContactInfo = require('./ContactInfo');
const User = require('./User');
const BlogPost = require('./BlogPost');

module.exports.associations = (sequelize) => {
  const user = User(sequelize);
  const contactInfo = ContactInfo(sequelize);
  const blogpost = BlogPost(sequelize);

  // Define your associations here

  // One-to-one relationship
  user.hasOne(contactInfo, {
    foreignKey: {
      type: DataTypes.UUID,
      allowNull: false,
    },
  });
  contactInfo.belongsTo(user);

  // One-to-many relationship
  user.hasMany(blogpost, {
    foreignKey: {
      type: DataTypes.UUID,
      allowNull: false,
    },
  });
  blogpost.belongsTo(user);

  // Many-to-many relationship
  user.belongsToMany(user, {
    as: 'User',
    foreignKey: 'UserId',
    through: 'Follow',
  });

  user.belongsToMany(user, {
    as: 'Followed',
    foreignKey: 'FollowedId',
    through: 'Follow',
  });
};

Upvotes: 0

Esteban
Esteban

Reputation: 180

Print typeof Entity where Entity is what Sequelize does not recognize as a model.

If you get undefined in the console, chances are that Entity is being exported incorrectly. For example, my problem was a typo: I wrote module.export instead of module.exports

Using Associate was not necessary in my case: for a many-to-many relationship, I used:

const EventReportCategory_EventReport = database.define(
            "event_report_category_event_report",
            {});
    
        EventReport.belongsToMany(EventReportCategory,
            {
                through: EventReportCategory_EventReport
            });
    
        EventReportCategory.belongsToMany(EventReport,
            {
                through: EventReportCategory_EventReport
            });

As soon as you wrap the mapping (in this case, a many-to-many mapping) in a function, and you call it before syncrhonizing Sequelize (new Sequelize(...).sync()), it is not necessary to define the entities in the same file where you do the mapping. In the previous example, EventReport and EventReportCategory are defined in different files.

Upvotes: 0

Ibraheem Adeyemo
Ibraheem Adeyemo

Reputation: 62

when I got this error, it happened that I defined my model using the class method. And I got a spelling error in some of the model files. I knew it was a spelling error when I consoled.log(models).

I just corrected the spelling and it fixes it.

 static associate(models) {
      // define association here
      console.log(models,'-=====')
      Category.belongsToMany(models.service, {through: 'serviceCategory'})
      Category.belongsToMany(models.Job, {through: 'JobCategory'})
  }

Upvotes: 2

nerdicsapo
nerdicsapo

Reputation: 427

None of these worked for me.

The reason I get error was

Employee.js

    const Company = require('./Company'); // you need to remove this line
    var Employee = sequelize.define('employee', {
     //define it
    });
    module.exports = Employee

Company.js

    const Employee = require('./Employee');
    var Company = sequelize.define('company', {
     //define it
    });
    Company.hasOne(Employee) // this line throws error.

Do not import Company in Employee's file!

It has been almost 4 years after question asked but it may help someone in the future.

Upvotes: 2

Stanislav Novikov
Stanislav Novikov

Reputation: 1

If you add multiple associations at the same time it may mess up the process. In my case I removed all the associations and added them one by one ending up with the same code that was producing this error before but works fine now.

Upvotes: 0

TRL
TRL

Reputation: 212

I'm new to Sequelize. This approach may be naïve, but it solves the chicken/egg problem in my simple use case:

models/Dog.js

const {Model} = require('sequelize');

module.exports = function (sequelize) {
  class Dog extends Model {}

  setTimeout(() => {
    Dog.hasMany(sequelize.models.Flea);
  }, 0);

  return Dog;
}

models/Flea.js

const {Model} = require('sequelize');

module.exports = function (sequelize) {
  class Flea extends Model {}

  setTimeout(() => {
    Flea.belongsTo(sequelize.models.Dog);
  }, 0);

  return Flea;
}

models/index.js:

const sequelize = getConnectionSomehow();

const Dog  = require('./Dog' )(sequelize);
const Flea = require('./Flea')(sequelize);

Upvotes: -1

atultw
atultw

Reputation: 1021

Putting all your models in one file isn't feasible if you have a lot of models.

What worked for me is not adding the models to the same file, but creating a new associations file and importing the models, then doing the belongsTo etc. in there. Then import './assocations' in the main app file. This resolved the error.

associations file would be something along the lines of this:

import {Post} from "./post";
import {Tag} from "./tag";

Tag.belongsToMany(Post, {
    through: 'PostTags'
})

Post.belongsToMany(Tag, {
    through: 'PostTags'
})

Upvotes: 5

This is how I solved it: A.hasMany(models.B); on the A model then on B model B.belongsTo(models.A);

"use strict";
const { Model } = require("sequelize");
const quotation = require("./quotation");
module.exports = (sequelize, DataTypes) => {
  class Clients extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
      Clients.hasOne(models.Quotation);
    }
  }
  Clients.init(
    {
      firstName: DataTypes.STRING,
      lastName: DataTypes.STRING,
      email: DataTypes.STRING,
      phone: DataTypes.STRING,
      contactName: DataTypes.STRING,
      contactPosition: DataTypes.STRING,
      rncCode: DataTypes.STRING,
      active: DataTypes.BOOLEAN,
    },
    {
      sequelize,
      modelName: "Clients",
    }
  );

  return Clients;
};

"use strict";
const { Model } = require("sequelize");
const quotation = require("./quotation");
module.exports = (sequelize, DataTypes) => {
  class Clients extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
      Clients.hasOne(models.Quotation);
    }
  }
  Clients.init(
    {
      firstName: DataTypes.STRING,
      lastName: DataTypes.STRING,
      email: DataTypes.STRING,
      phone: DataTypes.STRING,
      contactName: DataTypes.STRING,
      contactPosition: DataTypes.STRING,
      rncCode: DataTypes.STRING,
      active: DataTypes.BOOLEAN,
    },
    {
      sequelize,
      modelName: "Clients",
    }
  );

  return Clients;
};

Upvotes: 4

mohammad ravand
mohammad ravand

Reputation: 29

i found problem i forgot remove s from model name

User.belongsToMany(models.Roles, { through: "user_roles", });

Upvotes: 0

user8728077
user8728077

Reputation:

Putting A.hasOne(B) and B.belongsTo(A) in the same file solved the issue for me.

Upvotes: 40

juanignaciosl
juanignaciosl

Reputation: 3573

Just for future googlers, this can also happen with code that looks right but there's a circular dependency. In my case, A had a belongsTo relationship to B, and when I added a hook at B that instantiated A, the error appeared (in fact, it appears as you add the import).

I fixed it by adding the hook at A file.

Upvotes: 8

doublesharp
doublesharp

Reputation: 27677

You need to add your associations in a function called associate(models). The models argument contains all your existing Model definitions keyed by their definition name ("user_tm" in this case).

var User = sequelize.define('user_tm', {
  // ... user_tm definition
});

var UserEmployee = sequelize.define('user_employee_tm', {
  // ... user_employee_tm definition
});

UserEmployee.associate = (models) => {
  UserEmployee.belongsTo(models.user_tm, {foreignKey: 'ID', as: 'Employee'});
};

Upvotes: 13

Bagus Santoso
Bagus Santoso

Reputation: 481

Turns out I've found that I just need to define my Object of UserEmployee Here's the code that I've Fixed

const sequelize = require('../config/connectionDatabase');
var Sequelize = require('sequelize');
const User = require('../models/user');
const Company = require('../models/company');

var UserEmployee = sequelize.define('user_employee_tm', {
    DateJoin: {
        type: Sequelize.DATE
    },
    UserID: {
        type: Sequelize.INTEGER,
        references: {
            model: User,
            key: "UserID"
        }
    },
    CompanyID: {
        type: Sequelize.INTEGER,
        references: {
            model: Company,
            key: "CompanyID"
        }
    }
});
UserEmployee.belongsTo(Company, {as: 'Company', foreignKey: 'CompanyID'});
UserEmployee.belongsTo(User, {as: 'User', foreignKey: 'UserID'});
module.exports = UserEmployee;

no need to set as associate due to Sequelize has set method associate them, and I've also fix the relation of it.

hope others that has same problem with me could look after it, without make 2 models ends up on 1 file.

P.S. Thanks for doublesharp for your help to point my wrong doing

Upvotes: 7

Related Questions