Mongoose 中的 Getters/Setters
Mongoose 的 getters 和 setters 允許您在取得或設定 Mongoose 文件的屬性時執行自訂邏輯。Getters 讓您可以將 MongoDB 中的資料轉換為更方便使用者使用的格式,而 setters 讓您可以在使用者資料傳送到 MongoDB 之前先進行轉換。
Getters (取值器)
假設您有一個 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) {
// '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
請記住,getters 不會影響儲存在 MongoDB 中的基礎資料。如果您儲存 user
,則資料庫中的 email
屬性將為 'ab@gmail.com'。
預設情況下,Mongoose 在將文件轉換為 JSON 時,包括 Express 的 res.json()
函數, 不會執行 getters。
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 時執行 getters,請在您的結構描述中將 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 }));
});
若要一次性略過 getters,請使用 user.get()
,並將 getters
選項設為 false
,如下所示。
user.get('email', null, { getters: false }); // 'ab@gmail.com'
Setters (設定器)
假設您想要確保資料庫中的所有使用者電子郵件都轉換為小寫,以便在搜尋時不必擔心大小寫。以下是一個範例 userSchema
,它確保電子郵件為小寫。
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 也會在更新操作(例如 updateOne()
)中執行 setters。在以下範例中,Mongoose 將會 upsert (插入或更新) 一個文件,其中 email
為小寫。
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 文件,如下所示。
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
傳遞參數
您無法像對一般函數呼叫一樣將參數傳遞給 getter 和 setter 函數。若要設定或將其他屬性傳遞給您的 getters 和 setters,您可以使用文件的 $locals
屬性。
$locals
屬性是儲存任何程式定義的資料在您的文件上的首選位置,而不會與結構描述定義的屬性衝突。在您的 getter 和 setter 函數中,this
是正在存取的文件,因此您可以在 $locals
上設定屬性,然後在您的 getters 範例中存取這些屬性。例如,以下顯示如何使用 $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 Getters/Setters 的差異
Mongoose setters 與 ES6 setters 不同,因為它們允許您轉換正在設定的值。使用 ES6 setters,您需要儲存內部 _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 = 'TEST@gmail.com';
user.email; // undefined