はじめに
Mongooseでモデルを扱う際、インスタンスメソッドとスタティックメソッドという2つのメソッド定義方法があります。この記事では、両者の違いと使い分けについて解説します。
基本的な違い
インスタンスメソッドとスタティックメソッドの最大の違いは、どこに対して操作を行うかという点です。
インスタンスメソッド
モデルから生成された個々のドキュメントに対して操作を行います。メソッド内のthisは、特定のドキュメントインスタンスを指します。
スタティックメソッド
モデル全体に対して操作を行います。メソッド内のthisは、モデルクラス自体を指します。
定義方法と使用例
インスタンスメソッドの定義
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number,
createdAt: Date
});
// インスタンスメソッドの定義
userSchema.methods.getFullInfo = function() {
// thisは特定のユーザードキュメントを指す
return `${this.name} (${this.email})`;
};
userSchema.methods.isAdult = function() {
return this.age >= 18;
};
const User = mongoose.model('User', userSchema);
使用例
// 特定のユーザーを取得
const user = await User.findById(userId);
// インスタンスメソッドを呼び出す
const info = user.getFullInfo(); // "田中太郎 (tanaka@example.com)"
const adult = user.isAdult(); // true or false
スタティックメソッドの定義
// スタティックメソッドの定義
userSchema.statics.findByAge = function(age) {
// thisはUserモデル全体を指す
return this.find({ age: age });
};
userSchema.statics.findActiveAdults = function() {
return this.find({
age: { $gte: 18 },
isActive: true
});
};
使用例
// モデルクラスに対して直接呼び出す
const users25 = await User.findByAge(25);
const activeAdults = await User.findActiveAdults();
なぜ呼び出し方で動作が変わるのか
インスタンスメソッドが特定のドキュメントへの操作になる仕組みは、JavaScriptのthisの動作に基づいています。
仕組みの詳細
メソッドを呼び出す際、JavaScriptは自動的にthisを設定します。
// 異なるインスタンスで呼び出すと、異なる結果になる
const userA = await User.findOne({ name: '田中太郎' });
userA.getFullInfo(); // thisはuserAを指す → "田中太郎 (tanaka@example.com)"
const userB = await User.findOne({ name: '佐藤花子' });
userB.getFullInfo(); // thisはuserBを指す → "佐藤花子 (sato@example.com)"
同じメソッドでも、どのインスタンスから呼び出されたかによってthisが変わるため、結果も変わります。
一方、スタティックメソッドはモデルクラスから直接呼び出されるため、thisは常にモデル自体を指します。
User.findByAge(25); // thisはUserモデルを指す
用途の使い分け
インスタンスメソッドを使う場面
特定のドキュメントに関する操作を行う場合に使用します。
// そのドキュメントのデータを加工・整形
userSchema.methods.getDisplayName = function() {
return this.firstName + ' ' + this.lastName;
};
// そのドキュメントの状態を確認
userSchema.methods.canVote = function() {
return this.age >= 18 && this.isActive;
};
// そのドキュメント固有の計算
userSchema.methods.getDaysFromRegistration = function() {
return Math.floor((Date.now() - this.createdAt) / (1000 * 60 * 60 * 24));
};
スタティックメソッドを使う場面
複数のドキュメントやデータベース全体に関わる操作を行う場合に使用します。
// 複雑な検索クエリをカプセル化
userSchema.statics.findActiveAdults = function() {
return this.find({
age: { $gte: 18 },
isActive: true,
deletedAt: null
}).sort({ createdAt: -1 });
};
// 複数のドキュメントに対する一括操作
userSchema.statics.deactivateOldAccounts = function() {
const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
return this.updateMany(
{ lastLoginAt: { $lt: oneYearAgo } },
{ isActive: false }
);
};
// データベースレベルの集計処理
userSchema.statics.getAgeStatistics = function() {
return this.aggregate([
{ $group: { _id: null, avgAge: { $avg: '$age' } } }
]);
};
カプセル化のメリット
スタティックメソッドは、複雑な処理の詳細を隠して、シンプルなインターフェースだけを外部に見せるカプセル化に役立ちます。
カプセル化していない場合
// 毎回この複雑なクエリを書く必要がある
const activeAdultUsers = await User.find({
age: { $gte: 18 },
isActive: true,
deletedAt: null
}).sort({ createdAt: -1 });
// 別の場所でも同じクエリを繰り返す
const moreActiveAdultUsers = await User.find({
age: { $gte: 18 },
isActive: true,
deletedAt: null
}).sort({ createdAt: -1 });
問題点:
- 同じ複雑なクエリを何度も書く必要がある
- クエリの条件を変更したい時、すべての場所を修正する必要がある
- コードが読みにくい
カプセル化した場合
// スタティックメソッドで複雑なクエリをカプセル化
userSchema.statics.findActiveAdults = function() {
return this.find({
age: { $gte: 18 },
isActive: true,
deletedAt: null
}).sort({ createdAt: -1 });
};
// 使う側はシンプルに呼び出すだけ
const activeAdultUsers = await User.findActiveAdults();
const moreActiveAdultUsers = await User.findActiveAdults();
メリット:
- 使う側は複雑な条件を知らなくてよい
- メソッド名から何をするか分かりやすい
- 条件を変更する時は、メソッドの定義を1箇所変えるだけでよい
まとめ
-
インスタンスメソッド: 特定のドキュメントに対する操作。
thisは個々のドキュメントを指す -
スタティックメソッド: モデル全体に対する操作。
thisはモデルクラスを指す
「このドキュメントに対して何かをする」ならインスタンスメソッド、「このモデルのドキュメント群に対して何かをする」ならスタティックメソッドと覚えておくと良いでしょう。