14
votes

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

8

8 Answers

19
votes

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

11
votes

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'});
};
5
votes

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

4
votes

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;
};
2
votes

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.

1
votes

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'
})
0
votes

i found problem i forgot remove s from model name

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

0
votes

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);