我正在使用 Mongoose 模型,这是模型
import { Router, Request, Response } from 'express'
import UserModel from '../../../models/user.model'
import { error500 } from '../../../global/errors'
import AreaModel from '../../../models/area.model'
const UserChangesRoute = Router()
UserChangesRoute.get( '/:id', ( req: Request, res: Response ) => {
const id: String = req.params.id
const pop_user = { path: 'modification.user', select: 'name last_name' }
UserModel.find( { _id: id }, 'modification' ).sort({ 'modification.date': 1 })
.populate( pop_user )
.lean()
.exec( ( err: any, data: any ) => {
if( err ) {
return res.status( 500 ).json( { message: error500, err } )
}
// I used to: data = data.toObject()
for( let i = 0; i < data[0].modification.length; i++ ) {
if( data[0].modification[ i ][ 'updated' ][ 0 ] === 'area' ) {
AreaModel.findById( data[0].modification[ i ][ 'updated' ][ 1 ], 'name', ( e: any, area: any ) => {
if( !e ) data[0].modification[ i ][ 'updated' ][ 1 ] = area.name
});
AreaModel.findById( data[0].modification[ i ][ 'updated' ][ 2 ], 'name', ( e: any, area: any ) => {
if( !e ) data[0].modification[ i ][ 'updated' ][ 2 ] = area.name
});
}
}
res.status( 200 ).json( { data } )
})
})
export default UserChangesRoute
我正在尝试查找并替换它所在的数组的部分data[0].modification
在这里里面存储类似这样的东西
[ 'area', '5d144642416ada46385d01b8', '5d144681416ada46385d01b9', 'Área'];
最后应该是这样的
['area', 'Sistemas', 'Recursos Humanos', 'Área'];
索引 1 在 DB 结构级别表示它被移到前面。第二个索引是删除了哪些区域(在这种情况下),第三个索引是替换了哪些区域,最后一个只是前面的标记。
这是一部变化的历史。
我想要做的是用区域名称替换那些 Mongo id,我尝试用 a 来做populate
,但它不是一个数组Schemas
,因为它没有对 Areas 模型的引用,我重申它是一个改动历史,不光是保存区域,如果不是角色、名字、直属boss,反正……前面改动的所有东西,都必须这样存储,而且每次改动都是一个$push
安排modifications
现在,问题是 Mongoose 返回我 a MongoDocument
,而不是 a JSON
(无论它多么相似)我需要解析MongoDocument
aJSON
才能使用它。
这是用户模型(Mauricio Contreras 要求)
import mongos from 'mongoose'
import validator from 'mongoose-unique-validator'
const schema = new mongos.Schema({
name: {
type : String,
required : [ true, 'El nombre es necesario' ],
maxlength : [ 50, 'El nombre no puede exceder los 50 caracteres'],
minlength : [ 3, 'El nombre debe contener 3 o más caracteres']
},
last_name: {
type : String,
required : [ true, 'El apellido es necesario' ],
maxlength : [ 100, 'El apellido no puede exceder los 50 caracteres'],
minlength : [ 3, 'El apellido debe contener 3 o más caracteres'],
},
user_name: {
type: String,
unique : [ true, 'El usuario está duplicado' ],
required : [ true, 'El usuario es necesario' ],
maxlength : [ 25, 'El usuario no puede exceder los 25 caracteres'] },
email: {
type : String,
unique : [ true, 'El correo está duplicado'],
required : [ true, 'El correo es necesario' ],
maxlength : [ 100, 'El correo no puede exceder los 100 caracteres'],
match : [/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
'El correo electrónico no tiene el formato adecuado'] },
gender : { type: Number, min: 0, max: 2 },
photo : { type: String },
phone : { type: String },
role : { type: mongos.Schema.Types.ObjectId, ref: 'Role' },
area : { type: mongos.Schema.Types.ObjectId, ref: 'Area' },
boss : { type: mongos.Schema.Types.ObjectId, ref: 'User' },
permissions: [{
module : { type: mongos.Schema.Types.ObjectId, ref: 'Module' },
chmod : { type: String, minlength: 1, maxlength: 5, default: 'r' }
}],
password : { type: String },
status : { type: String, default: 'active' },
last_login : { type: Date },
addedBy : { type: String },
addedDate : { type: Date, default: Date.now },
modification : [{
_id : false,
user : { type: mongos.Types.ObjectId, ref: 'User' },
date : { type: Date, default: Date.now },
updated : { type: Array }
}]
}, { collection: 'users' })
schema.plugin( validator, { message: 'Ya existe el correo o ID {VALUE} en la base de datos' } )
const UserModel = mongos.model('User', schema )
export default UserModel
我尝试使用lean()方法,但它不适合我。
以与我使用toObject()相同的方式,但它告诉我它不是一个函数。
如何替换 MongoDocument 中的数据?
tl;博士
简短的回答
您可以使用聚合方法来实现您打算做的事情,我将尝试尽可能详细地向您解释这一点。但这将是一个相当长的解释。
我将使用聚合方法,分阶段执行任务,直到达到预期的结果。您可以在MongoDB文档中阅读有关聚合的更多信息。
使用您的问题中提供的数据作为模型,我们将编写一个聚合方法,当请求对用户进行更改时,我们将执行该方法,就像您在路由中所拥有的那样。
使用聚合 API 时的主要区别之一是它将返回一个
Array
包含简单 JSON 文档的文件,然后您可以在仍然需要时对其进行修改。另一方面,Queries
对模型进行的查询 ( ) 返回模型的一个实例(在本例中为Mongoose 文档),以及它的方法和属性。使用聚合:
一开始可能有点吓人,但是一旦我们理解了阶段和管道的概念,作为MongoDB 聚合过程的一部分,它就很容易了。
这样,文件将
Array
根据您的问题要求如您所愿地返回。长答案
简短回答说明
我反对这样的答案:复制粘贴这个解决方案,所以我会分阶段解释聚合方法,以便您了解我们在做什么。
阶段
$match
:id
第一阶段接收我们要在其上执行聚合过程的用户文档作为参数。需要注意的是,Mongoose 聚合 API在管道阶段不执行cast
数据类型转换,这就是我们将使用从String
返回req.params.id
类型ObjectId
转换为 Mongo 类型的原因。此阶段返回其字段
_id
与我们作为参数传递的值匹配的文档。可以等同于方法findById(id)
。阶段
$project
:在这个阶段,我们将创建我们想要的文档的基本结构。在这种情况下,我将您在问题中提出的内容作为参考。
由于该字段是
modification
typeArray
,为了使用它,我们将使用 operator$map
,它迭代我们数组的每个元素。对于数组的每个元素,我将指出我想要返回的字段。因此,如果我想要该字段
user
,我必须将其表示为:在这个阶段,关键是我们如何处理字段
updated
,它是一个类型Array
。在所述字段上,我们将使用运算符
$map
并将每个item
(如果可能)转换为 typeObjectId
。为此,我们将使用运算符$convert
。我们要做的是尝试将 的每个值转换
updated
为 ObjectId 类型。我说尝试是因为并非我们数组中的所有值都是可以转换为ObjectId
Mongo 类型的有效字符串。考虑到该字段
updated
包含类似于以下的值:String
很明显,我们数组的第一个和最后一个元素不是要转换为的有效值ObjectId
。这就是我们使用onError
operator 属性的原因$convert
,它允许我们在转换过程中出现错误时返回一个值:现在该字段
updated
将具有以下结构:这个问题的答案在舞台上给出
$lookup
。阶段
$unwind
:这个阶段将使我们在处理数组数据时变得更容易
modification
。这个阶段所做的是为我们数组中存在的每个元素创建一个文档,并将该字段modification
转换为 typedocument
。由于在看到结果之前很难理解该过程,因此我将留下一些可以澄清发生了什么的图像:
在执行阶段之前
$unwind
,我们的聚合如下图所示:我们可以注意到只有一个文档包含该字段中的所有修改数据
modification
。执行 step
$unwind
时,结果如下:Ahora claramente podemos observar que se han creado 3 documentos (1 por cada elemento del campo
modification
), además, el campomodification
pasó de ser un tipoArray
a ser un campo que contiene un documento.Ahora podremos trabajar sin problemas sobre el campo
updated
de cada documento.Etapa
$lookup
:En esta etapa lo que hacemos es algo parecido al JOIN de SQL. La idea es traer los datos de otra colección (aunque la misma no tenga una referencia directa con la que estamos procesando). Para ello debemos establecer el campo por el cual deseamos hacer la referencia.
Aquí cobra importancia la primera etapa de
$project
y la etapa de$unwind
, ya que vamos a buscar datos de la colecciónareas
usando como campo de referencia los valoresObjectId
del campoupdated
(el cual es un tipoArray
). Además, haremos una etapa$lookup
adicional para traer los datos de la colecciónusers
(si entiendo bien el propósito del campomodification
: se almacena el_id
del usuario que realizó la modificación).Siendo que
modification.updated
es un campo de tipoArray
, el proceso de$lookup
se llevará a cabo sobre cada elemento de dichoArray
, cada documento devuelto por$lookup
será añadido a unArray
llamadoupdated
que es el nombre que le hemos pasado como valor al atributoas
.Un ejemplo de nuestro documento hasta este momento sería el siguiente:
Como se aprecia en la imagen (sólo se muestra el primer documento), se han añadido 2 campos:
user
yupdated
, ambos contienen documentos de las coleccionesusers
yareas
que corresponden a los_id
sobre los que se ha hecho el$lookup
. Para este ejemplo, el modeloArea
es muy básico, solo contiene el camponame
, nuestro modeloUser
también es básico para usarlo en este ejemplo.Segunda etapa
$project
Ahora que ya tenemos los datos que necesitamos, pasaremos a la siguiente etapa. En esta, vamos a construir lo que se convertirá en nuestro documento final.
Tal vez visualmente esta etapa es muy compleja, pero lo cierto es que los procesos que allí se realizan son muy sencillos.
En la primera parte de la etapa declaramos el campo
modification
e incluimos dentro del mismo los siguientes valores:Debemos recordar que el campo
user
agregado en la etapa anterior es un tipoArray
que contiene los documentos encontrados al realizar la búsqueda sobre el campo_id
. Es por ello que se usa el operador$arrayElemAt
al cual se le pasa como parámetro la ruta delArray
($user
) y la posición del elemento que queremos obtener (en este caso el primer elemento). esto nos devolverá el documento que contiene los datos del usuario, algo equiparable al métodopopulate()
de Mongoose.Esto indica que se incluirá el campo
date
que hemos venido agregando en cada etapa.Por último, crearemos un campo llamado
updated
que contendrá los valores nuevos que hemos obtenido en la etapa de$lookup
.La construcción la haremos usando el operador
$map
, ya que el campoupdated
es de tipoArray
.El
Array
de entrada será el que hemos modificado en nuestra primera etapa de$project
, el cual contiene la siguiente estructura:Luego definiremos 2 variables:
Las mismas hacen referencia a los 2 documentos del campo
updated
que hemos creado durante la etapa de$lookup
. La idea es comparar el valor deObjectId
delArray
de origen con el valor deObjectId
del documento que hemos llenado de la colecciónareas
.Una vez establecidas nuestras variables realizaremos un retorno condicional, es decir, retornaremos un valor de acuerdo a si se cumple una condición o no. Para ello usaremos el operador
$cond
, usando la siguiente sintaxis:Nuestra expresión booleana será:
Donde
$$item
se refiere al valor del iterando de nuestroArray
de entrada, y$$from._id
será el valor del campo_id
de nuestro primer elemento.Si hay coincidencia devolvemos el valor de
$$from.name
, de lo contrario pasamos a la siguiente parte del condicional, donde realizaremos nuevamente una operación de comparación condicional para determinar si se devuelve el valor de$$to.name
o simplemente devolvemos el valor de$item
.Una vez realizadas todas estas tareas, el campo
updated
debe tener la siguiente estructura:Que es la estructura deseada.
Etapa
$sort
:Hasta ahora ya tenemos la estructura armada de nuestro documento, ahora vamos a ordenarla usando el campo
modification.date
en orden ascendente (fecha más antigua hacia la más reciente).Tercera etapa
$project
:En esta tercera y última etapa
$project
, vamos a sanitizar o sanear los datos que deseamos mostrar en el campouser
. Hasta ahora se muestran todos los campos del documento, pero deseamos mostrar solamente los campos_id
,name
ylast_name
. Es por ello que escribimos lo siguiente:En donde le indicamos a MongoDB que deseamos sólo el campo
name
y el campolast_name
. El campo_id
siempre se incluirá a menos que escribamos explícitamente:_id: 0
.Etapa
$group
:Esta será nuestra última etapa. Recordemos que durante la agregación separamos los documentos en tantos ítems como elementos tuviera el campo
modification
. Ahora es tiempo de reagrupar todos estos documentos, en uno solo y convertir nuevamente el campomodification
en unArray
, tal como aparece el documento original.Dado que todos los documentos creados en la etapa
$unwind
corresponden al mismo_id
de usuario, usaremos dicho campo como_id
de nuestra agrupación:En esta etapa le estamos diciendo a MongoDB que agrupe todos los documentos que contengan el mismo campo
_id
, y que en dicha agrupación, cree un campo llamadomodification
que será de tipoArray
ya que usaremos el operador$push
, para insertar cada valor del campomodification
de cada uno de los documentos que estamos agrupando.Dado que todos los documentos tienen el mismo
_id
, el resultado será un documento único, que contiene un campo de tipoArray
cuyos elementos se corresponden con los cambios realizados sobre el usuario, y donde hemos agregado los datos del usuario que realizó los cambios y además hemos cambiado los valores deid
de área por el nombre del área.Ejemplo de agregación antes de realizar la etapa de
$group
:Se puede apreciar que la agregación devuelve varios documentos, todos tienen el mismo valor para el campo
_id
.Ejemplo de agregación después de realizar la etapa de
$group
:Ahora se aprecia que la agregación devuelve un solo documento donde el campo
modification
es un tipoArray
, cuyos elementos se corresponden con cada modificación realizada sobre el usuario.RECOMENDACIONES FINALES
Una de las recomendaciones que puedo hacerte es que escribas la agregación como un método del modelo. De esta forma, puedes usarlo desde cualquier otro método en que lo necesites y así no tienes que repetir nada de código, sólo la llamada.
Puedes consultar la documentación sobre métodos de instancia de Mongoose, para mayor información, o consultar la API de mongoose sobre métodos de
Schema
.Por ejemplo podrías hacer lo siguiente en tu modelo:
user.model.js
Luego, para usar la agregación puedes hacer lo siguiente:
De esta forma tienes todo centralizado en el modelo
User
. Además, así como has escrito una agregación paraarea
, puedes escribirla pararoles
yjefes
, etc. Siempre que el valor deupdate
cumpla el formato especificado. Sólo cambias el valor de la colección sobre la que harás el$lookup
.Incluso puedes hacer una agregación que te devuelva todo, haciendo varias etapas
$lookup
, una por cada colección sobre la que desees obtener la información.Por ejemplo:
Como puedes ver, he encadenado varias etapas
$lookup
, cada una apunta a una colección diferente, luego debes ir refactorizando el código según sea necesario.La ventaja de usar la agregación sobre lo que intentabas hacer (realizando una consulta a la DB por cada
id
en el campoupdated
), es que la agregación implica una sola llamada a la base de datos, todos los procesos se realizan del lado del motor de base de datos, y en teoría serán más rápidos que realizar el proceso manualmente desde tu aplicación.假设一个用户在修改字段中有 10 个(可以说很少)条目,这意味着您必须向数据库发出 20 个请求(每个条目 2 个)才能获取与
id
每个条目关联的字段名称的信息。入口。这根本不切实际,不计算你必须做的链接,或者使用async
能够await
与 DB 调用一起使用的函数。我希望这可以解决您的问题,并帮助您稍微了解聚合的世界。