No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Convierte tus certificados en títulos universitarios en USA

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

19 Días
0 Hrs
59 Min
27 Seg

Relaciones uno a muchos referenciadas

22/24
Recursos

Es crucial diseñar bien tu base de datos. Las relaciones uno a muchos pueden crecer indefinidamente y es importante considerar la escalabilidad de tu base de datos.

Relaciones uno a muchos embebidas vs. referenciadas

A lo igual que las relaciones uno a uno, las relaciones uno a muchos requieren contemplar la posible actualización de todo ese array de documentos. Tener un array de objetos puede no ser escalable y una no muy buena decisión de arquitectura de la base de datos.

Estos problemas se solucionan creando arrays de referencias. Un simple array de string que contendrá un item por cada ID al cual deseas hacer referencia dentro de un objeto principal.

Relaciones referenciadas

Veamos como puedes crear este tipo de relación donde un documento contendrá un array de IDs que hacen referencia a otros documentos de la base de datos.

Paso 1: preparación del esquema

Prepara la propiedad correspondiente en tu esquema que contendrá el array de referencias.

// users/order.entity.ts
import { Document, Types } from 'mongoose';
import { Product } from '../../products/entities/product.entity';

@Schema()
export class Order extends Document {

  @Prop({ type: [{ type: Types.ObjectId, ref: Product.name }] })
  products: Types.Array<Product>;
}

Observa que el decorador @Prop() recibe como tipo un Types.ObjectId que a su vez se encuentra encerrado por un array []. También, tienes que tipar la propiedad con Types.Array<> proveniente desde mongoose.
De esta simple manera, Mongoose sabe que la propiedad products contiene un array de MongoID.

Paso 2: preparar el DTO para la validación de datos

El DTO solo necesita recibir un array de string[] que es el equivalente para un array de MongoID.

// users/order.dto.ts
import { IsMongoId, IsNotEmpty, IsDate, IsArray } from 'class-validator';
import { OmitType, PartialType } from '@nestjs/swagger';

export class CreateOrderDto {

  @IsArray()
  @IsNotEmpty()
  readonly products: string[];
}

Utiliza el decorador @IsArray() para validar que efectivamente se trate de un array.

Paso 3: GET de documentos referenciados

Realizar un “Join” o, como se lo conoce en MongoDB, un “Populate” es muy sencillo. Basta con agregar la configuración después del método de búsqueda indicando el nombre de la propiedad a popular.

// users/orders.service.ts

export class OrdersService {

  constructor(@InjectModel(Order.name) private orderModel: Model<Order>) {}

  findAll() {
    return this.orderModel
      .find()
      .populate('products')
      .exec();
  }
}

Mongoose sabrá a qué colección ir a buscar los documentos al estar referenciado y tipado desde el esquema.

De esta manera, tu base de datos está lista para manipular grandes volúmenes de datos con los mejores tipos de relaciones que existen. Utiliza el tipo de relación más apropiado para cada escenario.

Contribución creada por: Kevin Fiorentino (Platzi Contributor).


Código de ejemplo de relaciones uno a muchos referenciadas

// src/users/entities/order.entity.ts
import { Document, Types } from 'mongoose';

import { Customer } from './customer.entity';
import { Product } from '../../products/entities/product.entity';

@Schema()
export class Order extends Document {
   ...
  @Prop({ type: Types.ObjectId, ref: Customer.name, required: true })
  customer: Customer | Types.ObjectId; // 👈 relation 1:1 customer

  @Prop({ type: [{ type: Types.ObjectId, ref: Product.name }] })
  products: Types.Array<Product>; // 👈 relation 1:N
}
// src/users/dtos/order.dto.ts
import { IsMongoId, IsNotEmpty, IsDate, IsArray } from 'class-validator';
import { OmitType, PartialType } from '@nestjs/swagger';  // 👈 use OmitType

export class CreateOrderDto {
  @IsNotEmpty()
  @IsMongoId()
  readonly customer: string;

  @IsDate()
  @IsNotEmpty()
  readonly date: Date;

  @IsArray()
  @IsNotEmpty()
  readonly products: string[];
}

export class UpdateOrderDto extends PartialType(
  OmitType(CreateOrderDto, ['products']),  // 👈 implement OmitType
) {}
// src/users/services/orders.service.ts

export class OrdersService {
  constructor(@InjectModel(Order.name) private orderModel: Model<Order>) {}

  findAll() {
    return this.orderModel
      .find()
      .populate('customer') // 👈 join customer 1:1
      .populate('products') // 👈 join products 1:N
      .exec();
  }
}

Aportes 15

Preguntas 5

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Excelente curso!!!

Para los que quieran popular, las skills tienen que usar un populate anidado

    return this.orderModel
      .find()
      .populate('productIds)
      .populate({
        path: 'customer',
        populate: {
          path: 'skills',
        },
      })
      .exec();

¿Qué pasa si después de haber echo una Orden se modifica el precio de un producto?

Si se actualiza el precio de alguno de estos productos, al estar referenciado, la orden ya no tendría el precio con el cual se compro el producto inicialmente.

Al día de hoy ya no es necesario colocar el .exec()

Usando mongoose 8.3.3 y @nestjs/mongoose 10.0.6 no me funcionaba el `.populate('products')`. Si alguien tiene una solución similar a la de la clase me comenta, gracias de antemano. Por el momento me funcionó lo siguiente: 1\. Exportar MongooseModule para usar el modelo Product en OrdersService ```ts // src/products/products.module.ts exports: [ProductsService, MongooseModule], ``` 2\. Inyectar el modelo y modificar el método findAll() ```ts // src/users/services/orders.service.ts import { Product } from 'src/products/entities/product.entity'; // ... // ... constructor( @InjectModel(Order.name) private orderModel: Model<Order>, @InjectModel(Product.name) private productModel: Model<Product>, ) {} // ... // ... async findAll() { const orders = await this.orderModel.find(); for (const order of orders) { await Promise.all( order.products.map(async (productId, index) => { const product = await this.productModel.findById(productId); order.products[index] = product; }), ); } return orders; } ```
Hola! Al parecer ha cambiado el mongoose y lo de clase no funciona, pero hay 2 formas de resolverlo, escoge la que prefieras o puedes usar ambas (aunque sería redundante) **Primera:** habría que cambiar en el Modelo Schema - Document, si ves, el Objeto dentro del @Prop(), tiene un atributo que es "ref", en el curso lo ponen dentro del objeto en el atributo "type", pero realmente debería ir por fuera, como en las otras referencias, algo así: `@Prop({ type: [{ type: Types.ObjectId }], ref: Product.name})` `products: Types.``Array``<Products>` **Segunda:** Es indicarle la colección a la que debe ir a buscar el **populate en el Servicio**, algo así: `.populate('products', null, Products.name)` **\*\*** El "null" de en medio es para el campo "select" del populate, donde van los campos que quiero filtrar del documento, pero en este caso no quiero "filtrar" los datos sino que quiero todos entonces lo pongo en "null" (si no lo pones no funcionará porque el 3er campo es el que recibe la colección)
Usando mongoose 8.3.3 y @nestjs/mongoose 10.0.6 no funcionaba el populate('products'). Si alguien tiene una solución similar a la de la clase me comenta, gracias de antemano. Por el momento a mi me funcionó lo siguiente: 1\. Exportar MongooseModule para usar el modelo Product en OrdersService ```ts // src/products/products.module.ts exports: [ProductsService, MongooseModule], ``` 2\. Inyectar el modelo y modificar el método findAll() ```ts // src/users/services/orders.service.ts import { Product } from 'src/products/entities/product.entity'; // ... // ... constructor( @InjectModel(Order.name) private orderModel: Model<Order>, @InjectModel(Product.name) private productModel: Model<Product>, ) {} // ... // ... async findAll() { const orders = await this.orderModel.find(); for (const order of orders) { await Promise.all( order.products.map(async (productId, index) => { const product = await this.productModel.findById(productId); order.products[index] = product; }), ); } return orders; } ```
Usando mongoose 8.3.3 y @nestjs/mongoose 10.0.6 no funcionaba el populate('products'). Si alguien tiene una solución similar a la de la clase me comenta, gracias de antemano. A mi me funcionó lo siguiente: 1. Exportar MongooseModule para usar el modelo Product en OrdersService 2. Inyectar el modelo y modificar el método findAll() ```ts // src/products/products.module.ts exports: \[ProductsService, MongooseModule], ``` ```ts // src/users/services/orders.service.ts import { Product } from 'src/products/entities/product.entity'; // ... // ... constructor( @InjectModel(Order.name) private orderModel: Model\<Order\>, @InjectModel(Product.name) private productModel: Model\<Product\>, ) {} // ... // ... async findAll() { const orders = await this.orderModel.find(); for (const order of orders) { await Promise.all( order.products.map(async (productId, index) =\> { const product = await this.productModel.findById(productId); order.products\[index] = product; }), ); } return orders; } ```
```ts dsdas ``````ts ```
```ts sdsad ```sadasdas

Primera vez que me pasa esto en insomnia y no se a que se debe.

Excelente curso, enserio!!

Usando mongoose 8.3.3 y @nestjs/mongoose 10.0.6 no me funcionaba el .populate('products'). Si alguien tiene una solución similar a la de la clase me comenta, gracias de antemano. Por el momento me funcionó lo siguiente: 1\. Exportar MongooseModule para usar el modelo Product en OrdersService ```ts // src/users/services/orders.service.ts import { Product } from 'src/products/entities/product.entity'; ``` 2\. Inyectar el modelo y modificar el método findAll() ```ts // src/users/services/orders.service.ts import { Product } from 'src/products/entities/product.entity'; // ... // ... constructor( @InjectModel(Order.name) private orderModel: Model<Order>, @InjectModel(Product.name) private productModel: Model<Product>, ) {} // ... // ... async findAll() { const orders = await this.orderModel.find(); for (const order of orders) { await Promise.all( order.products.map(async (productId, index) => { const product = await this.productModel.findById(productId); order.products[index] = product; }), ); } return orders; } ```
Usando mongoose 8.3.3 y @nestjs/mongoose 10.0.6 no funcionaba el populate('products'). Si alguien tiene una solución similar a la de la clase me comenta, gracias de antemano. A mi me funcionó lo siguiente: 1. Exportar MongooseModule para usar el modelo Product en OrdersService 2. Inyectar el modelo y modificar el método findAll() ```ts // src/products/products.module.ts exports: [ProductsService, MongooseModule], ``````js // src/users/services/orders.service.ts import { Product } from 'src/products/entities/product.entity'; // ... // ... constructor( @InjectModel(Order.name) private orderModel: Model<Order>, @InjectModel(Product.name) private productModel: Model<Product>, ) {} // ... // ... async findAll() { const orders = await this.orderModel.find(); for (const order of orders) { await Promise.all( order.products.map(async (productId, index) => { const product = await this.productModel.findById(productId); order.products[index] = product; }), ); } return orders; } ```
Usando mongoose 8.3.3 y @nestjs/mongoose 10.0.6 no funcionaba el `populate('products')`. No sé si alguien encontró la solución. A mi me funcionó lo siguiente: 1\. Exportar MongooseModule para usar el modelo Product en OrdersService ```ts // src/products/products.module.ts exports: [ProductsService, MongooseModule], ```2. Inyectar el modelo y modificar el método findAll() ```ts // src/users/services/orders.service.ts import { Product } from 'src/products/entities/product.entity'; // ... // ... constructor( @InjectModel(Order.name) private orderModel: Model<Order>, @InjectModel(Product.name) private productModel: Model<Product>, ) {} // ... // ... async findAll() { const orders = await this.orderModel.find(); for (const order of orders) { await Promise.all( order.products.map(async (productId, index) => { const product = await this.productModel.findById(productId); order.products[index] = product; }), ); } return orders; } ```Si alguien tiene una solución similar a la de la clase me comenta, gracias de antemano.