我有以下用于跟踪习惯的模式,以及用户的进度
习惯模式
const HabitSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
title: {
type: String,
required: true,
},
type: {
type: String,
enum: ["Goal", "Limit"],
default: "Goal",
required: true,
},
dateStarted: {
type: Date,
default: Date.now,
required: true,
},
});
进度计划:每次用户点击他们在给定时间段(天、周、月等(完成了一个习惯,就会为该时间段创建一个文档,以表明已经完成。
const ProgressSchema = new mongoose.Schema({
habit: {
type: mongoose.Schema.Types.ObjectId,
ref: "Habit",
},
period: {
type: Date,
default: Date.now,
required: true,
},
value: {
type: Number,
default: 1,
required: true,
},
});
当我了解用户的所有习惯时,我还想了解每个习惯的所有进度文档。我想在返回之前加入这些对象。
以下是我尝试过的:
let habits = await Habit.find({ user: req.user.id }).sort({
dateStarted: -1,
});
habits = await Promise.all(
habits.map(async (h, i) => {
try {
const progress = await Progress.find({ habit: h._id });
return { ...h, progress };
} catch (e) {
res.status(500).send("Server Error");
}
})
);
res.json({ habits });
这会产生错误:CastError: Cast to ObjectId failed for value "undefined" at path "_id" for model "Habit"
实现这一目标的正确方法是什么?
我仍然不明白为什么会出现错误,因为我使用了wait/async,所以在检索到id之前,该行不应该运行(错误是说_id未定义,而事实显然不是(。
在尝试了很多事情之后,以下是最终奏效的:
try {
await Promise.all(
habits.map(async (h, i) => {
let progress = await Progress.find({ habit: h._id });
let newHabit = h;
newHabit._doc.progresses = progress;
return newHabit;
})
);
} catch (error) {
console.log(error);
res.status(500).send("Server Error");
}
一旦我终于让await Progress.find({ habit: h._id });
工作起来,我还发现我不能用扩散运算符直接添加到猫鼬检索的对象中。对象有许多属性,数据库中的字段实际上包含在中_对象的doc属性:newHabit._doc.progresses = progress
(但这与我得到的错误无关(。这让我认为有一种更好的方法可以将字段添加到mongoose.find()
找到的文档中。
更好的解决方案是使用.lean((,因为您没有运行任何像.save这样的函数,这样您就不需要find查询返回的附加项。
let habits = await Habit.find({ user: req.user.id }).sort({
dateStarted: -1,
}).lean();
habits = await Promise.all(
habits.map(async (h, i) => {
try {
const progress = await Progress.find({ habit: h._id }).lean();
return { ...h, progress };
} catch (e) {
res.status(500).send("Server Error");
}
})
);
res.json({ habits });
更好的解决方案是使用ref并填充
我还没有使用Mongoose,但是,在vanilla MongoDB中,你可以使用这样的聚合框架来实现这一点:
db.Habbit.aggregate([
{ $lookup: {
from: "Progress",
localField: "_id",
foreignField: "habbit",
as: "Progresses"
} }
])
此外,您还可以添加一个步骤来丢弃除最新进展外的所有进展,而不是全部返回。然而,我将把这个留给另一个问题。