No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Aprende todo un fin de semana sin pagar una suscripci贸n 馃敟

Reg铆strate

Comienza en:

3D
20H
45M
43S

Resolviendo relaciones muchos a muchos

22/27
Recursos

Aportes 19

Preguntas 11

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

Ha sido genial este curso hasta aqui, bastante largo, pero uff me siento capaz de crear un CRUD con entero con bdd relaciones gracias a este curso 馃槃

Tape datos porque sin querer puse los reales en algunos campos JAJAJAJA

Para una aplicaci贸n m谩s profesional o 鈥渟eria鈥 yo recomendar铆a realizar el calculo de la orden de compra y ese calculo guardarlo en la base de datos, as铆 para cuando el cliente requiera una aclaraci贸n, una factura o un estado de cuenta se tenga toda la informaci贸n almacenada. Incluso para una auditoria es mejor tener toda la historia de los datos dentro de la base de datos. Si se calcula un dato al vuelo como en esta clase es probable que en alg煤n punto de la historia este dato se pierda y puede ocasionar un dolor de cabeza tremendo cuando se requiera cualquier tipo de aclaraci贸n.

Profe, una correcci贸n en la url del m茅todo agregar items a un orden rompe el principio 鈥淪ustantivos s铆, verbos no鈥 del siguiente post. Creo que es importante mantener la coherencia en el contenido que se publica.

La ruta en mi opini贸n deber铆a ser algo como:

Method: "post"
Action: "api/v1/orders/:id/products"

Error de edicion:

Minuto 9:40
Se repite el contenido (:

Antes de agregar el producto a la order ser铆a bueno validar que el producto exista, algo as铆:

async addItem(data) {
    const product = await productService.findOne(data.productId);

    if (!product) {
      throw boom.notFound('product not found');
    }

    return await models.OrderProduct.create(data);
  }

si vas a mover tu proyecto o volver a crear la base de datos, te recomiendo remover el total, ya que al hacer migrate sequelize te dice que el tipo VIRTUAL no existe. y despues de creado todo se lo vuelve a poner al modelo.

Desde mi experiencia considero que se est谩n haciendo alguno usos err贸neos de las migraciones. Por ejemplo, el campo virtual no funciona si se corren las migraciones desde 0. Se debe especificar en el Schema pero no en las migraciones. Por ejemplo en RoR las migraciones se crean para modificar una entidad del sistema, normalmente no se agregan varios create a una sola migraci贸n por cuestiones de pr谩cticas.

Es enserio lo del campo virtual, por si al alguien le pasa ya sabe por qu茅 es 馃槂

Hola, les comparto un c贸digo adicional que hice para tener un endpoint que me devuelva todas las 贸rdenes para corroborar los datos de prueba:

En orders.router.js:

router.get('/', async (req, res) => {
  try {
    const orders = await service.find()
    res.json(orders)
  } catch (error) {
    res.status(404).json({
      message: error.message
    })
  }
})

En services/order.service.js:

  async find() {
    const orders = await models.Order.findAll({
      include: [
        {
          association: 'customer',
          include: ['user']
        },
        'items'
      ]
    })
    return orders
  }

Se agrega producto a la orden de compra, para ello de asignan nuevas validaciones en el schema de la orden.

order.schema.js:

const Joi = require('joi');

const id = Joi.number().integer();
const customerId = Joi.number().integer();
const orderId = Joi.number().integer();
const productId = Joi.number().integer();
const amount = Joi.number().integer().min(1);

const getOrderSchema = Joi.object({
  id: id.required(),
});

const createOrderSchema = Joi.object({
  customerId: customerId.required(),
});

const addItemSchema = Joi.object({
  orderId: orderId.required(),
  productId: productId.required(),
  amount: amount.required(),
});

module.exports = {
  getOrderSchema,
  createOrderSchema,
  addItemSchema,
};

Tambi茅n se crea el post en el router de la orden para agregar algo a la orden de compra deseada.

orders.router.js:

router.post(
  '/add-item',
  validatorHandler(addItemSchema, 'body'),
  async (req, res, next) => {
    try {
      const body = req.body;
      const newItem = await service.create(body);
      res.status(201).json(newItem);
    } catch (error) {
      next(error);
    }
  }
);

Posteriormente se crea el m茅todo en la clase OrderService.

order.service.js:

async addItem(data) {
    const newItem = await models.OrderProduct.create(data);
    return newItem;
  }

La respuesta de la API arroja el precio del producto y la cantidad de items, se puede calcular el total de esa orden de compra. Para ello hay una propiedad de sequelize en donde se puede generar un total y datos calculados.

El atributo no va a existir como campo en la tabla y se debe especificar que el campo es de tipo virtual (DataTypes.VIRTUAL), esto es recomendable 煤nicamente para campos peque帽os, pero cuando son campos grandes no es recomendable, lo mejor ser铆a hacer una query porque ser谩 m谩s r谩pida ya que va a calcular directamente desde la base de datos.

Con el m茅todo get() se va a especificar c贸mo se calcula ese campo. Algo que se debe tener en cuenta es que en this.items.length, el nombre de items debe ser el mismo con el que se haya nombrado la asociaci贸n (as: 'items')

total: {
    type: DataTypes.VIRTUAL,
    get() {
      if (this.items.length > 0) {
        return this.items.reduce((total, item) => {
          return total + (item.price * item.OrderProduct.amount);
        }, 0);
      }
      return 0;
    },
  },

Me costo mucho esta clase

Hecho!

Field: VIRTUAl + Reduce -> para calcular valores en tiempo de ejecucion. Y devolverlos en el endpoint.

莽ampo virtual

Para quienes quieran explorar un poco las relaciones n:m (muchos a muchos):
Sequelize nos permite usar las relaciones que definimos en las Associations para crear instancias de varios modelos relacionados de forma anidada. Ejemplo:
Digamos que tenemos dos modelos, cliente y producto definidos como en la clase, cada uno en su archivo cliente.model.js y producto.model.js

  • Un cliente puede tener muchos productos
  • Un mismo producto puede ser enlistado por muchos clientes
    = relaci贸n muchos a muchos
// Esquema del cliente
const ClienteSchema = {
    id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: DataTypes.INTEGER
    },
    nombre: {
        allowNull: false,
        type: DataTypes.STRING
    },
}

// Definimos el modelo
class Cliente extends Model {
    static associate(models) {
        // Asociaciones
        const relacionClienteProducto = this.belongsToMany(models.Cliente, {
            through: models.ClienteProducto,
            foreignKey: 'clienteId',
            otherKey: 'productoId'
        });
        return relacionClienteProducto
    }

    static config(sequelize) {
        return {
            sequelize,
            tableName: CLIENTE_TABLE,
            modelName: 'Cliente',
            timeStamps: false
        }
    }
}
export {
    CLIENTE_TABLE, ClienteSchema, Cliente
}
// la documentaci贸n dice que para una relaci贸n muchos a muchos se debe hacer la asociaci贸n belongsToMany en ambos modelos, en cliente.model y producto.model

N贸tese c贸mo guardamos la asociaci贸n en una variable 鈥渞elacionClienteProducto鈥, pues esta es la que vamos a usar para crear los clientes y productos de forma anidada, para ello debemos hacer llegar esta variable a nuestros servicios, que siguiendo la estructura de carpetas de la clase, podr铆a ser as铆:

en el archivo db/models/index.js

// importamos los modelos
export function setupModels(sequelize) {
    // ... inicializamos los modelos como en la clase y luego:
    const relacionClienteProducto = Cliente.associate(sequelize.models)
};
// guardamos el return del associate de este modelo en una nueva variable para exponerla en

// libs/sequelize

const relacionClienteProducto= setupModels(sequelize);
sequelize.sync();
export { sequelize, relacionClienteProducto}

y usarla en los servicios:

async crearCliente(data) {
        const {
            nombre,
            Productos
        } = data;

        const clienteYProductos = await sequelize.models.Cliente.create({
                nombre,
                Productos: Productos
            },
            {
                include: [{
                    association: relacionClienteProducto
                }]
            });
            return clienteYProductos ;
        }
    }

Aqu铆 suponemos que se cre贸 un schema y modelo para Producto similar al de Cliente, y sequelize pluraliza los campos (no se si en espa帽ol, pero en ingl茅s lo hace), de modo que si el nombre que le dimos al modelo de los productos fue de 鈥淧roduct鈥, el campo para colocar el req.body ser铆a 鈥淧roducts鈥.

De esta forma podemos crear un cliente y con una sola operaci贸n a帽adir cuantos productos queramos, dependiendo de c贸mo definamos sus modelos. Espero no haber sido muy enredado, aqu铆 la doc:
https://sequelize.org/docs/v6/advanced-association-concepts/creating-with-associations/

Ojito con crear la tabla con el campo total de tipo virtual, eh! que te bota todo. Lo agregan despu茅s de crear la tabla no m谩s.

Para los que quieran guardar el total de los productos en la orden, deber谩n quitar el 鈥淰IRTUAL鈥 por 鈥淔LOAT鈥, la funci贸n 鈥済et()鈥 no va funcionar y tampoco si creas una funci贸n en el 鈥渄efaultValue鈥, por lo que se tendr铆a que realizar las acciones para guardarlo en el servicio de orden, este es la manera en la cu谩l lo hice y me funciono

  async addItem(data) {
    //crear el pedido
    const newItem = await models.OrderProduct.create(data);
    //obtener el pedido mediante data.orderId que es el objeto obtenido desde la petici贸n
    let orderTotal = await this.findOne(data.orderId);
    //buscar el precio del producto que esta en la orden
    const productoOrdenado = await productService.findOne(data.productId);
    //multiplicar el precio por la cantidad
    const precioTotal = data.amount * productoOrdenado.dataValues.price;
    //sumar el valor al total
    orderTotal.dataValues.total += precioTotal;
    //realizar el cambio de total
    await models.Order.update(orderTotal.dataValues, {
      where: { id: data.orderId }
    });
    //al final se va ir sumando mientras m谩s items agreguemos
    return newItem;
  }

鈥渄ataValue鈥 viene a ser otro objeto que guarda los par谩metros de la data.

Si tu como yo, llegaste hasta ac谩 utilizando de motor de bd a mysql y no hiciste el cambio a postgres y te marca este error al correr las migraciones : Cannot add foreign key constraint, cambia de inmediato a postgres, por alguna raz贸n todo funciona de maravilla all谩, as铆 que es algo del motor en especifico

Para el caso que necesiten obtener la relaci贸n sin traer datos de la tabla de uni贸n como en mi caso necesitaba saber todos los cursos que ten铆a un perfil asociado, pero sin traer el objeto que profileCourse por defecto pueden utilizar:

through: {attributes: []}
const data = await models.Profile.findByPk(id, {
  include: [
    { 
      model: models.User, 
      as: 'user',
      attributes: ['id', 'email']
    },
    { 
      model: models.Course, 
      as: 'courses',
      through: {attributes: []}
    }
  ]
});

I loved this class