Mongoose 中的 Getter/Setter
Mongoose 的 getter 和 setter 允许您在获取或设置 Mongoose 文档 上的属性时执行自定义逻辑。Getter 允许您将 MongoDB 中的数据转换为更友好的形式,而 Setter 允许您在用户数据进入 MongoDB 之前对其进行转换。
Getter
假设您有一个 User
集合,并且您希望对用户电子邮件进行混淆以保护用户的隐私。以下是一个基本的 userSchema
,它会对用户的电子邮件地址进行混淆。
const userSchema = new Schema({
email: {
type: String,
get: obfuscate
}
});
// Mongoose passes the raw value in MongoDB `email` to the getter
function obfuscate(email) {
const separatorIndex = email.indexOf('@');
if (separatorIndex < 3) {
// '[email protected]' -> '**@gmail.com'
return email.slice(0, separatorIndex).replace(/./g, '*') +
email.slice(separatorIndex);
}
// '[email protected]' -> 'te****@gmail.com'
return email.slice(0, 2) +
email.slice(2, separatorIndex).replace(/./g, '*') +
email.slice(separatorIndex);
}
const User = mongoose.model('User', userSchema);
const user = new User({ email: '[email protected]' });
user.email; // **@gmail.com
请记住,getter 不会影响存储在 MongoDB 中的底层数据。如果您保存 user
,则 email
属性在数据库中将是 '[email protected]'。
默认情况下,Mongoose 在将文档转换为 JSON 时不会执行 getter,包括 Express 的 res.json()
函数。
app.get(function(req, res) {
return User.findOne().
// The `email` getter will NOT run here
then(doc => res.json(doc)).
catch(err => res.status(500).json({ message: err.message }));
});
要将 getter 应用于将文档转换为 JSON 时,请将 toJSON.getters
选项设置为您的模式中的 true
,如下所示。
const userSchema = new Schema({
email: {
type: String,
get: obfuscate
}
}, { toJSON: { getters: true } });
// Or, globally
mongoose.set('toJSON', { getters: true });
// Or, on a one-off basis
app.get(function(req, res) {
return User.findOne().
// The `email` getter will run here
then(doc => res.json(doc.toJSON({ getters: true }))).
catch(err => res.status(500).json({ message: err.message }));
});
要在一个事件中跳过 getter,请使用 user.get()
并将 getters
选项设置为 false
,如下所示。
user.get('email', null, { getters: false }); // '[email protected]'
Setter
假设您希望确保数据库中的所有用户电子邮件都是小写的,以便您可以轻松搜索而不用担心大小写。以下是一个示例 userSchema
,它确保电子邮件是小写的。
const userSchema = new Schema({
email: {
type: String,
set: v => v.toLowerCase()
}
});
const User = mongoose.model('User', userSchema);
const user = new User({ email: '[email protected]' });
user.email; // '[email protected]'
// The raw value of `email` is lowercased
user.get('email', null, { getters: false }); // '[email protected]'
user.set({ email: '[email protected]' });
user.email; // '[email protected]'
Mongoose 还将在更新操作(如 updateOne()
)中运行 setter。在下面的示例中,Mongoose 将 使用小写 email
插入文档。
await User.updateOne({}, { email: '[email protected]' }, { upsert: true });
const doc = await User.findOne();
doc.email; // '[email protected]'
在 setter 函数中,this
可以是正在设置的文档或正在运行的查询。如果您不希望 setter 在您调用 updateOne()
时运行,您可以添加一个 if 语句来检查 this
是否是 Mongoose 文档,如下所示。
const userSchema = new Schema({
email: {
type: String,
set: toLower
}
});
function toLower(email) {
// Don't transform `email` if using `updateOne()` or `updateMany()`
if (!(this instanceof mongoose.Document)) {
return email;
}
return email.toLowerCase();
}
const User = mongoose.model('User', userSchema);
await User.updateOne({}, { email: '[email protected]' }, { upsert: true });
const doc = await User.findOne();
doc.email; // '[email protected]'
使用 $locals
传递参数
您不能像对普通函数调用那样将参数传递给 getter 和 setter 函数。要配置或将附加属性传递给 getter 和 setter,您可以使用文档的 $locals
属性。
$locals
属性是存储您在文档上定义的任何程序定义数据(而不与模式定义的属性冲突)的首选位置。在 getter 和 setter 函数中,this
是正在访问的文档,因此您在 $locals
上设置属性,然后在 getter 示例中访问这些属性。例如,以下显示了如何使用 $locals
来配置自定义 getter 的语言,该 getter 以不同的语言返回字符串。
const internationalizedStringSchema = new Schema({
en: String,
es: String
});
const ingredientSchema = new Schema({
// Instead of setting `name` to just a string, set `name` to a map
// of language codes to strings.
name: {
type: internationalizedStringSchema,
// When you access `name`, pull the document's locale
get: function(value) {
return value[this.$locals.language || 'en'];
}
}
});
const recipeSchema = new Schema({
ingredients: [{ type: mongoose.ObjectId, ref: 'Ingredient' }]
});
const Ingredient = mongoose.model('Ingredient', ingredientSchema);
const Recipe = mongoose.model('Recipe', recipeSchema);
// Create some sample data
const { _id } = await Ingredient.create({
name: {
en: 'Eggs',
es: 'Huevos'
}
});
await Recipe.create({ ingredients: [_id] });
// Populate with setting `$locals.language` for internationalization
const language = 'es';
const recipes = await Recipe.find().populate({
path: 'ingredients',
transform: function(doc) {
doc.$locals.language = language;
return doc;
}
});
// Gets the ingredient's name in Spanish `name.es`
assert.equal(recipes[0].ingredients[0].name, 'Huevos'); // 'Huevos'
与 ES6 Getter/Setter 的区别
Mongoose setter 不同于 ES6 setter,因为它们允许您转换正在设置的值。使用 ES6 setter,您需要存储一个内部 _email
属性来使用 setter。使用 Mongoose,您不需要定义内部 _email
属性或为 email
定义相应的 getter。
class User {
// This won't convert the email to lowercase! That's because `email`
// is just a setter, the actual `email` property doesn't store any data.
// also eslint will warn about using "return" on a setter
set email(v) {
// eslint-disable-next-line no-setter-return
return v.toLowerCase();
}
}
const user = new User();
user.email = '[email protected]';
user.email; // undefined