Mongoose のゲッター/セッター
Mongoose のゲッターとセッターを使用すると、Mongoose ドキュメントのプロパティを取得または設定するときにカスタムロジックを実行できます。ゲッターを使用すると MongoDB のデータをより使いやすい形式に変換でき、セッターを使用すると、ユーザーデータが MongoDB に到達する前に変換できます。
ゲッター
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
ゲッターは MongoDB に格納された基本的なデータには影響しないことに注意してください。user
を保存すると、データベース内の email
プロパティは 'ab@gmail.com' になります。
既定では、Mongoose はドキュメントを JSON に変換するときに 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 }));
});
ドキュメントを 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 }));
});
一時的にゲッターをスキップするには、getters
オプションを false
に設定した user.get()
を使用します(以下を参照)。
user.get('email', null, { getters: false }); // 'ab@gmail.com'
セッター
データベース内のすべてのユーザーメールを小文字にすることで、大文字小文字を心配することなく簡単に検索できるようにしたいとします。以下はメールが小文字になるよう保証する 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()
のような更新操作でもセッターを実行します。Mongoose は、次の例のように、小文字の email
を使用してドキュメントを 更新します。
await User.updateOne({}, { email: 'TEST@gmail.com' }, { upsert: true });
const doc = await User.findOne();
doc.email; // 'test@gmail.com'
セッター関数では、this
は設定されているドキュメントまたは実行されているクエリーのいずれかになります。updateOne()
を呼び出したときにセッターを実行したくない場合は、this
が Mongoose ドキュメントであるかどうかをチェックする if ステートメントを追加します(以下を参照)。
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
を使用してパラメーターを渡す
普通の関数呼び出しのようにゲッター関数とセッター関数にパラメーターを渡すことはできません。ゲッターとセッターに構成または追加のプロパティを渡すには、ドキュメントの $locals
プロパティを使用できます。
$locals
プロパティは、プログラムで定義されたデータをスキーマで定義されたプロパティと競合せずにドキュメントに格納するために推奨される場所です。ゲッター関数とセッター関数では、this
はアクセスするドキュメントのため、$locals
にプロパティを設定し、ゲッターの例でそれらのプロパティにアクセスします。たとえば、$locals
を使用して、さまざまな言語で文字列を返すカスタムゲッターの言語を構成する方法を以下に示します。
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 ゲッター/セッターとの違い
Mongoose のセッターは ES6 セッター とは異なります。ES6 セッターでは、セットされる値を変換することができます。ES6 セッターでは、セッターを使用するために内部 _email
プロパティを格納する必要があります。Mongoose では、内部 _email
プロパティを定義する必要はありません。また、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