Home > Mobile >  Sequelizenot mapping correctly belongs to many
Sequelizenot mapping correctly belongs to many

Time:11-17

I have a Categories table, Attributes and an intermediate table, AttributeCategory, which has id, attribute_id, category_id, date and value, the problem is that when sequelize maps the relationship if the attribute_id and category_id match it does not map it correctly, example:

Category table: id = 1, description = "some"
Attribute table: id = 1, description = "one", id = 2, description = "two"

Category Attribute Table,
id = 1, category_id = 1, attribute_id = 1, value = 10, date = 2020-05-10
id = 2, category_id = 1, attribute_id = 1, value = 15, date = 2020-05-15
id = 3, category_id = 1, attribute_id = 2, value = 10, date = 2020-05-10  

The query to db does it correctly, but when it maps the object it only returns me:
id = 2, category_id = 1, attribute_id = 1, value = 15, date = 2020-05-15
id = 3, category_id = 1, attribute_id = 2, value = 10, date = 2020-05-10  

as the same attribute_id and category_id are repeated, it only returns the last one.  

My models are:

    // Atrributes
    'use strict';
    const { Model } = require('sequelize');
    
    module.exports = (sequelize, DataTypes) => {
      class InmAtributo 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) {
          // Atributo tiene varias categorias
          InmAtributo.belongsToMany(models.InmCategoria, {
            as: 'categorias',
            through: models.InmAtributoCategoria,
            foreignKey: 'atributo_id',
            otherKey: 'categoria_id'
          });
        }
      };
      InmAtributo.init({
        descripcion: DataTypes.STRING,
        abreviado: DataTypes.STRING
      }, {
        sequelize,
        modelName: 'InmAtributo',
        tableName: 'inm_atributos',
        timestamps: false,
      });
      return InmAtributo;
    };



    //Categorys
    'use strict';
    const { Model } = require('sequelize');
    
    module.exports = (sequelize, DataTypes) => {
      class InmCategoria 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) {
          // Categoria tiene varios atributos
          InmCategoria.belongsToMany(models.InmAtributo, {
            as: 'atributos',
            through: models.InmAtributoCategoria,
            foreignKey: 'categoria_id',
            otherKey: 'atributo_id'
          });
    
        }
      };
      InmCategoria.init({
        descripcion: DataTypes.STRING,
        abreviado: DataTypes.STRING
      }, {
        sequelize,
        modelName: 'InmCategoria',
        tableName: 'inm_categorias',
        timestamps: false,
      });
      return InmCategoria;
    };


    //AtributoCategoria
    'use strict';
    const { Model } = require('sequelize');
    
    module.exports = (sequelize, DataTypes) => {
      class InmAtributoCategoria 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) {
    
        }
      };
      InmAtributoCategoria.init({
        id: {
          type: DataTypes.INTEGER,
          primaryKey: true,
          autoIncrement: true,
          allowNull: false
        },
        valor: DataTypes.FLOAT,
        fecha_activo: DataTypes.DATE,
      }, {
        sequelize,
        modelName: 'InmAtributoCategoria',
        tableName: 'inm_atributo_categoria',
        timestamps: true,
      });
      return InmAtributoCategoria;
    };

My migrations are:

    //Attributes
    'use strict';
    module.exports = {
      up: async (queryInterface, Sequelize) => {
        await queryInterface.createTable('inm_atributos', {
          id: {
            allowNull: false,
            autoIncrement: true,
            primaryKey: true,
            type: Sequelize.INTEGER
          },
          descripcion: {
            allowNull: false,
            type: Sequelize.STRING(100)
          },
          abreviado: {
            allowNull: false,
            type: Sequelize.STRING(5)
          }
        });
      },
      down: async (queryInterface, Sequelize) => {
        await queryInterface.dropTable('inm_atributos');
      }
    };
    
    
    //Categorys
    'use strict';
    module.exports = {
      up: async (queryInterface, Sequelize) => {
        await queryInterface.createTable('inm_categorias', {
          id: {
            allowNull: false,
            autoIncrement: true,
            primaryKey: true,
            type: Sequelize.INTEGER
          },
          descripcion: {
            allowNull: false,
            type: Sequelize.STRING(100)
          },
          abreviado: {
            allowNull: false,
            type: Sequelize.STRING(5)
          }
        });
      },
      down: async (queryInterface, Sequelize) => {
        await queryInterface.dropTable('inm_categorias');
      }
    };
    
    
    //AtributoCategoria
    'use strict';
    module.exports = {
      up: async (queryInterface, Sequelize) => {
        await queryInterface.createTable('inm_atributo_categoria', {
          id: {
            allowNull: false,
            autoIncrement: true,
            primaryKey: true,
            type: Sequelize.INTEGER
          },
          atributo_id: {
            allowNull: false,
            references: {
              model: "inm_atributos",
              key: "id"
            },
            type: Sequelize.BIGINT
          },
          categoria_id: {
            allowNull: false,
            references: {
              model: "inm_categorias",
              key: "id"
            },
            type: Sequelize.BIGINT
          },
          fecha_activo: {
            allowNull: false,
            type: Sequelize.DATE
          },
          valor: {
            allowNull: false,
            type: Sequelize.FLOAT
          },
          created_at: {
            type: Sequelize.DATE
          },
          updated_at: {
            type: Sequelize.DATE
          }
        });
      },
      down: async (queryInterface, Sequelize) => {
        await queryInterface.dropTable('inm_atributo_categoria');
      }
    };

My function in controller:

    async show(req, res) {
            try {
                const inmCategoria = await InmCategoria.findByPk(req.params.id, {
                    include: {
                        association: 'atributos',
                        through: {
                            attributes: ['categoria_id', 'atributo_id', 'valor', 'fecha_activo', 
                            'updated_at', 'created_at']
                        }
                    }
                });
    
                return res.json({ 'status': 'Exito', 'body': inmCategoria })
            } catch (error) {
                return res.json({ 'status': 'Error', 'body': error.message })
            }
        },


Result
Query generated automatically

    SELECT "InmCategoria"."id", "InmCategoria"."descripcion", "InmCategoria"."abreviado", 
    "atributos"."id" AS "atributos.id", "atributos"."descripcion" AS "atributos.descripcion", 
    "atributos"."abreviado" AS "atributos.abreviado", "atributos->InmAtributoCategoria"."id" AS         
    "atributos.InmAtributoCategoria.id", "atributos->InmAtributoCategoria"."categoria_id" AS     
    "atributos.InmAtributoCategoria.categoria_id", "atributos->InmAtributoCategoria"."atributo_id" 
    AS "atributos.InmAtributoCategoria.atributo_id", "atributos->InmAtributoCategoria"."valor" AS 
    "atributos.InmAtributoCategoria.valor", "atributos->InmAtributoCategoria"."fecha_activo" AS 
    "atributos.InmAtributoCategoria.fecha_activo", "atributos->InmAtributoCategoria"."updated_at" 
    AS "atributos.InmAtributoCategoria.updated_at", "atributos->InmAtributoCategoria"."created_at" 
    AS "atributos.InmAtributoCategoria.created_at" FROM "inm_categorias" AS "InmCategoria" LEFT 
    OUTER JOIN ( "inm_atributo_categoria" AS "atributos->InmAtributoCategoria" INNER JOIN 
    "inm_atributos" AS "atributos" ON "atributos"."id" = "atributos- 
    >InmAtributoCategoria"."atributo_id") ON "InmCategoria"."id" = "atributos- 
    >InmAtributoCategoria"."categoria_id" WHERE "InmCategoria"."id" = '10';

JSON output

    {
      "body": {
        "id": "10",
        "descripcion": "ZONE A01",
        "abreviado": "A01",
        "atributos": [
          {
            "id": "10",
            "descripcion": "Additional wasteland",
            "abreviado": "ABAL",
            "InmAtributoCategoria": {
              "categoria_id": "10",
              "atributo_id": "10",
              "valor": 50,
              "fecha_activo": "2010-01-01",
              "updated_at": "2021-10-06T19:11:27.000Z",
              "created_at": "2021-10-06T19:11:27.000Z"
            }
          },
          {
            "id": "21",
            "descripcion": "Environmental rate",
            "abreviado": "TAMIN",
            "InmAtributoCategoria": {
              "categoria_id": "10",
              "atributo_id": "21",
              "valor": 120,
              "fecha_activo": "2014-01-01",
              "updated_at": "2021-10-06T19:11:27.000Z",
              "created_at": "2021-10-06T19:11:27.000Z"
            }
          }
        ]
      }
    }

Query result in navicat

id  descripcion abreviado   atributos.id    atributos.descripcion   atributos.abreviado atributos.InmAtributoCategoria.id   atributos.InmAtributoCategoria.categoria_id atributos.InmAtributoCategoria.atributo_id  atributos.InmAtributoCategoria.valor    atributos.InmAtributoCategoria.fecha_activo atributos.InmAtributoCategoria.updated_at   atributos.InmAtributoCategoria.created_at


    |id|descripcion|abreviado|atributos.id|atributos.descripcion|atributos.abreviado|atributos.InmAtributoCategoria.id|atributos.InmAtributoCategoria.categoria_id|atributos.InmAtributoCategoria.atributo_id|atributos.InmAtributoCategoria.valor|atributos.InmAtributoCategoria.fecha_activo|atributos.InmAtributoCategoria.updated_at|atributos.InmAtributoCategoria.created_at|
    |10|ZONA A01|A01|10|ADICIONAL BALDIO|ABAL|167|10|10|60|2021-01-01|2021-10-06 16:11:27|2021-10-06 16:11:27|
    |10|ZONA A01|A01|21|TASA AMBIENTAL MINIMO|TAMIN|175|10|21|265|2019-01-01|2021-10-06 16:11:27|2021-10-06 16:11:27|
    |10|ZONA A01|A01|21|TASA AMBIENTAL MINIMO|TAMIN|179|10|21|800|2021-01-01|2021-10-06 16:11:27|2021-10-06 16:11:27|


Thanks! Cheers!

CodePudding user response:

If you use category_id and attribute_id as foreign keys for the N:M join table then they cannot be the same in two different records because it violates N:M type of relationship between tables.
If you want to store several records with the same pair of values category_id and attribute_id then you need hasMany from InmCategoria to InmAtributoCategoria and belongsTo from InmAtributoCategoria to InmAtributo like this:

class InmCategoria extends Model {
...
static associate(models) {
          InmCategoria.hasMany(models.InmAtributoCategoria, {
            as: 'atributoCategoria',
            foreignKey: 'categoria_id'
          });
}
class InmAtributoCategoria extends Model {
...
static associate(models) {
          InmAtributoCategoria.belongsTo(models.InmAtributo, {
            as: 'atributo',
            foreignKey: 'atributo_id'
          });

}

And the query will look like this:

const inmCategoria = await InmCategoria.findByPk(req.params.id, {
  include: {
    association: 'atributoCategoria',
    attributes: ['categoria_id', 'atributo_id', 'valor', 'fecha_activo', 
                            'updated_at', 'created_at'],
    include: {
      association: 'atributo'
    }
  }
});
  • Related