Mongoose 中的 getter/setter

¥Getters/Setters in Mongoose

Mongoose getter 和 setter 允许你在获取或设置 Mongoose 文档 上的属性时执行自定义逻辑。Getter 可让你将 MongoDB 中的数据转换为更用户友好的形式,而 setter 可让你在用户数据到达 MongoDB 之前对其进行转换。

¥Mongoose getters and setters allow you to execute custom logic when getting or setting a property on a Mongoose document. Getters let you transform data in MongoDB into a more user friendly form, and setters let you transform user data before it gets to MongoDB.

获取器

¥Getters

假设你有一个 User 集合,并且你想要混淆用户电子邮件以保护用户的隐私。下面是一个基本的 userSchema,它混淆了用户的电子邮件地址。

¥Suppose you have a User collection and you want to obfuscate user emails to protect your users' privacy. Below is a basic userSchema that obfuscates the user's email address.

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) {
    // 'ab@gmail.com' -> '**@gmail.com'
    return email.slice(0, separatorIndex).replace(/./g, '*') +
      email.slice(separatorIndex);
  }
  // 'test42@gmail.com' -> '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: 'ab@gmail.com' });
user.email; // **@gmail.com

请记住,getter 不会影响存储在 MongoDB 中的基础数据。如果保存 user,则 email 属性在数据库中将为“ab@gmail.com”。

¥Keep in mind that getters do not impact the underlying data stored in MongoDB. If you save user, the email property will be 'ab@gmail.com' in the database.

默认情况下,Mongoose 在将文档转换为 JSON 时不执行 getter,包括 Express 的 res.json() 函数

¥By default, Mongoose does not execute getters when converting a document to JSON, including Express' res.json() function.

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 }));
});

要在将文档转换为 JSON 时运行 getter,请设置 toJSON.getters option to true in your schema,如下所示。

¥To run getters when converting a document to JSON, set the toJSON.getters option to true in your schema as shown below.

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() with the getters option set to false,如下所示。

¥To skip getters on a one-off basis, use user.get() with the getters option set to false as shown below.

user.get('email', null, { getters: false }); // 'ab@gmail.com'

设置器

¥Setters

假设你想确保数据库中的所有用户电子邮件都是小写,以便于搜索而无需担心大小写。以下是确保电子邮件为小写的示例 userSchema

¥Suppose you want to make sure all user emails in your database are lowercased to make it easy to search without worrying about case. Below is an example userSchema that ensures emails are lowercased.

const userSchema = new Schema({
  email: {
    type: String,
    set: v => v.toLowerCase()
  }
});

const User = mongoose.model('User', userSchema);

const user = new User({ email: 'TEST@gmail.com' });
user.email; // 'test@gmail.com'

// The raw value of `email` is lowercased
user.get('email', null, { getters: false }); // 'test@gmail.com'

user.set({ email: 'NEW@gmail.com' });
user.email; // 'new@gmail.com'

Mongoose 还在更新操作上运行 setter,例如 updateOne()。在下面的示例中,Mongoose 会将 更新插入文档 与小写的 email 结合起来。

¥Mongoose also runs setters on update operations, like updateOne(). Mongoose will upsert a document with a lowercased email in the below example.

await User.updateOne({}, { email: 'TEST@gmail.com' }, { upsert: true });

const doc = await User.findOne();
doc.email; // 'test@gmail.com'

在 setter 函数中,this 可以是正在设置的文档,也可以是正在运行的查询。如果你不希望在调用 updateOne() 时运行 setter,请添加一个 if 语句来检查 this 是否是 Mongoose 文档,如下所示。

¥In a setter function, this can be either the document being set or the query being run. If you don't want your setter to run when you call updateOne(), you add an if statement that checks if this is a Mongoose document as shown below.

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: 'TEST@gmail.com' }, { upsert: true });

const doc = await User.findOne();
doc.email; // 'TEST@gmail.com'

使用 $locals 传递参数

¥Passing Parameters using $locals

你无法像普通函数调用那样将参数传递给 getter 和 setter 函数。要配置其他属性或将其他属性传递给 getter 和 setter,你可以使用文档的 $locals 属性。

¥You can't pass parameters to your getter and setter functions like you do to normal function calls. To configure or pass additional properties to your getters and setters, you can use the document's $locals property.

$locals 属性是在文档中存储任何程序定义的数据而不与结构定义的属性发生冲突的首选位置。在 getter 和 setter 函数中,this 是正在访问的文档,因此你在 $locals 上设置属性,然后在 getter 示例中访问这些属性。例如,下面显示了如何使用 $locals 为返回不同语言字符串的自定义 getter 配置语言。

¥The $locals property is the preferred place to store any program-defined data on your document without conflicting with schema-defined properties. In your getter and setter functions, this is the document being accessed, so you set properties on $locals and then access those properties in your getters examples. For example, the following shows how you can use $locals to configure the language for a custom getter that returns a string in different languages.

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 的差异

¥Differences vs ES6 Getters/Setters

Mongoose setter 与 ES6 设置器 不同,因为它们允许你转换正在设置的值。对于 ES6 setter,你需要存储内部 _email 属性才能使用 setter。使用 Mongoose,你不需要定义内部 _email 属性或为 email 定义相应的 getter。

¥Mongoose setters are different from ES6 setters because they allow you to transform the value being set. With ES6 setters, you would need to store an internal _email property to use a setter. With Mongoose, you do not need to define an internal _email property or define a corresponding getter for email.

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 = 'TEST@gmail.com';

user.email; // undefined