はじめに
mongooseは、pluginという形でモデルに機能を追加する事が出来ます。
例えば以下のように記述する事で、userModelに新しくfindOrCreateというメソッドが生えます。
import mongoose = require('mongoose');
import Schema = mongoose.Schema;
import Document = mongoose.Document;
import Model = mongoose.Model;
interface UserDocument extends Document{
name:String;
age:Number;
}
var userSchema:Schema = new Schema({name:String, age:Number});
//プラグインをモデルに突き刺す
userSchema.plugin(require('mongoose-findorcreate'));
var userModel:Model<UserDocument> = <Model<UserDocument>>mongoose.model('User', userSchema);
//プラグインでモデルに追加したメソッドを呼ぶ
userModel.findOrCreate({},function(){}); //The property 'findOrCreate' does not exist on value of type 'mongoose.Model<UserDocument>'
ところが、当然ながらmongoose.d.tsで定義されているModelにはfindOrCreateメソッドが生えていないため、userModelで呼び出す事が出来ません。
外部モジュールの中で定義されたinterfaceは外から拡張できないようなので、これを呼べるようにするにはanyでモデルを受け取るか、Modelを継承したUserModelを作ればいいと考えました。
某リポジトリのmongoose.d.tsでは、Modelは以下のように定義されています。
export interface Model<T extends Document> {
new(doc: Object): T;
//いろいろ続く
//…
}
受け取れる型をDocumentを継承した型に限定しています。なので継承してやればいいと思い以下のコードを書いてみたのですが、コンパイルエラーになりました。
interface UserModel<T extends Document> Model<T extends Document> {//'{' expected. ';' expected.
findOrCreate(opt:{}, callback:Function):void;
}
ジェネリッククラスの継承
先ほどの例ではどうやら構文解析に失敗しているようです。いろいろ調べてみたところ、ジェネリッククラスの継承には以下のような条件がある事が分かりました。
-
interface B<T> extends A<T>
のように、Tが何も継承していない時 -
interface B<String> extends A<String>
のように、型が定まっている時- この場合、
interface B extends A<String>
というように継承する側の型は省略してよさげ
- この場合、
Tが何かを継承しているとコンパイルにミスするみたい です。理由はわからんです。
いったん解決
で、今回の例ですが、UserModelはDocumentを継承したUserDocumentだけを受け取る事にしたいため、そのようにインターフェースを定義したうえで、以下のコードになりました。
//importやUserDocumentの宣言はtest1.tsと同じなので省略
interface UserModel extends Model<UserDocument> {
findOrCreate(opt:{}, callback:Function):void;
}
mongoose.connect('localhost');
var userSchema:Schema = new Schema({name:String, age:Number});
userSchema.plugin(require('mongoose-findorcreate'));
var userModel:UserModel = <UserModel>mongoose.model('User', userSchema);
//モデルを新たに作る
userModel.findOrCreate({name: 'ORZNGO', age:17}, (err:any, doc:UserDocument, created:Boolean):void => {
console.log(created);//true(作られた)
userModel.findOrCreate({name: 'ORZNGO', age:17}, (err:any, doc:UserDocument, created:Boolean):void => {
console.log(created);//false(既にあるので作られない)
});
});
コンパイルとおりました。 UserModelが複数の型を受け取れるようにする方法が未だに分かっていない気がしますが、いったん目的は果たせました。
既にドキュメントが存在する場合には作らない、というメソッドの挙動も確認できました。
補完にもきちんと出ています。やったぜ。