ThatCoderGuy
ThatCoderGuy

Reputation: 677

Sequelize - trying to make models dynamic

I've been trying to automate the creation of my sequelize models by creating them with a generic model that I can pass definitions into, rather than creating a model file specifically for each one.

I have an array of model definitions which looks something like this:

const modelDefinitions = [
    {
        name: "User",
        fieldDefinitions: [
            {
                name: "first_name",
                label: "First Name",
                column_type: Sequelize.DataTypes.STRING,
            },
            {
                name: "last_name",
                label: "Last Name",
                column_type: Sequelize.DataTypes.STRING,
            },
            {
                name: "email",
                label: "Email",
                column_type: Sequelize.DataTypes.STRING,
            },
            {
                name: "password",
                label: "Password",
                restricted: true,
                column_type: Sequelize.DataTypes.STRING,
            },
        ],
    },
    {
        name: "Audit",
        fieldDefinitions: [
            {
                name: "ref",
                column_type: Sequelize.DataTypes.STRING,
                label: "Audit Ref",
            },
            {
                name: "result",
                column_type: Sequelize.DataTypes.STRING,
                label: "Result",
            },
            {
                name: "auditor_id",
                column_type: Sequelize.DataTypes.INTEGER,
                label: "Auditor",
            },
        ],
    },
];

When my array of models contains just one model it works perfectly fine, but when I have multiple, the GenericModel of the previously defined models is then "changed" to ne the last one in the list that was initialised.

I'm new to node so I think I'm either missing something or there's some sort of model caching happening, meaning that all instances of GenericModel become what it is initialised as last.

Please see my example below (the commented out code is what I used to use to define the models and the reduce is my new way of defining these)

// {
//     User: User.init(sequelize, modelDef),
//     Article: Article.init(sequelize, modelDef),
//     Audit: Audit.init(sequelize, modelDef),
//     Form: Form.init(sequelize, modelDef),
// };

const models = modelDefinitions.reduce((acc, modelDef) => {
    return { ...acc, [modelDef.name]: GenericModel.init(sequelize, modelDef) };
}, {});

console.log({ models });

My console.log() returns the following - notice both are Group :

{ 
 models: {
   User: Group,
   Group: Group
 }
}

As you can see, what ever the last model is defined as, the previous ones inherit that instead of keeping what I defined them as originally.

But what I actually want is :

{ 
 models: {
   User: User,
   Group: Group
 }
}

If my list only had User in it, it works fine.

Upvotes: 2

Views: 1295

Answers (1)

ThatCoderGuy
ThatCoderGuy

Reputation: 677

I managed to get this working in the end.

I think my issue was that my GenericModel was treated as a Singleton, so to get around this I changed GenericModel from extending the Sequelize.Model and instead made a new class with a contructor to consume my arguments and then created a method on the new class to return the sequelize model.

The main change there was instead of defining the models with GenericModel.init(), I defined them by calling sequelize.define(modelName, attributes, options)

so my map now looks like this :

const models = modelDefinitions.reduce((acc, modelDef) => {
    return { ...acc, [modelDef.name]: new GenericModel(sequelize, modelDef).getDBModel() };
}, {});

and my class:

class TestModel {
    constructor(sequelize, modelDef) {
        this.sequelize = sequelize;
        this.modelDef = modelDef;
        this.modelName = modelDef?.name;
        this.definitions = modelDef?.fieldDefinitions;
        this.restrictedFieldList = this.definitions.filter((field) => field?.restricted).map((definition) => definition.name);
    }

    getDBModel() {
        const model = this.sequelize.define(
            this.modelName,
            this.definitions.reduce((acc, definition) => {
                return { ...acc, [definition.name]: definition.column_type };
            }, {}),
            {
                defaultScope: {
                    attributes: {
                        exclude: this.restrictedFieldList,
                    },
                },
                sequelize: this.sequelize,
                modelName: this.modelName,
            }
        );
        return model;
    }
}```

Upvotes: 2

Related Questions