概要
操作フック は特定のメソッドに束縛されず、作成・読取・更新・削除に関する高レベルの操作を実行するすべてのメソッドから起動されます。
対象は、アプリケーションモデルが継承する 永続化モデル のすべてのメソッドです。
操作フックを使用すると、それらの操作を呼び出す特定のメソッド(たとえばcreate
・save
・updateOrCreate
)に関係なく、データを変更するアクションを割り込ませることができます。
APIは単純です。メソッドModel.observe(name, observer)
で、name
は操作フックの名前の文字列、例えば「before save」など、そして、
observer
は function observer(context, callback)
です。オブザーバーを継承する子供のモデルは、複数のオブザーバーを登録できます。
次の表は、PersistedModelのcreate・retrieve・update・deleteメソッドによって呼び出される操作フックをまとめたものです。
メソッド → 操作フック ↓ |
find findOne findById |
exists | count | create | upsert | findOrCreate | deleteAll deleteById |
updateAll | prototype .save |
prototype .delete |
prototype .updateAttributes |
prototype .replaceAttributes |
replaceById | replaceOrCreate | upsertWithWhere |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
access | X | X | X | X | X | X | X | X | X | ||||||
before save | X | X | X | X | X | X | X | X | X | X | |||||
after save | X | X | X | X | X | X | X | X | X | X | |||||
before delete | X | X | |||||||||||||
after delete | X | X | |||||||||||||
loaded | X | X | X | X | X | X | X | X | X | X | X | X | |||
persist | X | X | X | X | X | X | X | X | X | X |
操作フックコンテキストオブジェクト
context
オブジェクトは操作フックに固有で、Model.beforeRemote
やModel.afterRemote
を介して登録されたリモートフックに渡されるコンテキストオブジェクトとは何の関係もありません。詳細については、リモートフック を参照してください。
コンテキストオブジェクトは、loopback.getCurrentContext()
によって提供される「現在のコンテキスト」にも関係しないことに注意してください。
すべてのフックと操作に共通のプロパティ
ターゲットモデル
context.Model
プロパティは、操作の対象となるモデルのコンストラクタに設定されます。例えば、Product.find()
では context.Model = Product
をセットします。
操作オプション
モデルの特定のメソッド(操作)の呼び出し元が提供するオプションに、フックがアクセスできるようにするために、コンテキストオブジェクトには options
プロパティがあります。
例えば、以下のようになります。
var FILTERED_PROPERTIES = ['immutable', 'birthday'];
MyModel.observe('before save', function filterProperties(ctx, next) {
if (ctx.options && ctx.options.skipPropertyFilter) return next();
if (ctx.instance) {
FILTERED_PROPERTIES.forEach(function(p) {
ctx.instance.unsetAttribute(p);
});
} else {
FILTERED_PROPERTIES.forEach(function(p) {
delete ctx.data[p];
});
}
next();
});
// immutable is not updated
MyModel.updateOrCreate({
id: 1,
immutable: 'new value'
}, cb);
// immutable is changed
MyModel.updateOrCreate({
id: 2,
immutable: 'new value'
}, {
skipPropertyFilter: true
}, cb);
共有フック状態プロパティ
フック間(たとえば、「before save」と「after save」)でデータを共有するには、ctx.hookState
プロパティを使用します。ctx.hookState
プロパティの値は、1回の操作で呼び出されるすべてのフックで保持されます。
例えば、MyModel.create()
は「access」「before save」「after save」フックを呼出しますが、それらは ctx.hookState
に同じオブジェクトを持ちます。
対照的に、ctx.options
はMyModel.find()
やMyModel.create()
のようなPersistedModelのメソッドが提供するoptions引数を使用して設定されます。
options引数が指定されなかった場合は、ctx.options
は空のオブジェクトに設定されるため、フックは ctx.options
が設定されているかどうかを確認する必要がありません。
フックと操作固有のプロパティ
上記の共通のプロパティに加えて、各フックは、操作の影響を受けるモデルインスタンスと適用された変更を識別する追加のプロパティを提供します。
一般的な規則は、コンテキストがinstance
プロパティまたは一組のdata
とwhere
プロパティのいずれかを提供します。
instance
このプロパティは、操作が単一のインスタンスに影響を与え、かつ すべてのモデルプロパティについて完全な更新・作成・削除が実行された時に、提供されます。
例えば、PersistedModel.create()
などです。
where + data
操作が複数のインスタンスに影響を与える場合(例えば、PersistedModel.updateAll()
)または モデルプロパティの一部に部分的な更新が行われる場合(例えば、PersistedModel.prototype.updateAttributes()
)、コンテキストは、影響を受けたレコードを見つけるために使われるwhere filterと、
なされるべき変更を含むdata
オブジェクトを提供する。
isNewInstance
いくつかの操作は、CREATE操作とUPDATE操作を区別するためのフラグを提供します。詳細については、個々のフックのドキュメントを参照してください。
Important:
特定のコネクタのみ ctx.isNewInstance
をサポートしています。他のコネクタでは未定義です。
ctx.isNewInstance サポートの確認を参照してください。
currentInstance
このプロパティは、単一インスタンスの部分的な変更を実行するフックによって提供されます。 影響を受けるモデルインスタンスが含まれているため、値を読み取り専用(不変)として扱う必要があります。
ctx.isNewInstance サポートの確認
ctx.isNewInstance
の最初の実装では、メモリ・MongoDB・MySQLコネクタのみがサポートされていました。
“after save”フックで返された値をテストすることで、コネクタがこの機能をサポートしているかどうかを確認することができます。
例えば以下のようにします。
MyModel.observe('after save', function(ctx, next) {
console.log('supports isNewInstance?', ctx.isNewInstance !== undefined);
next();
});
// It's important to provide a value for the id property
// Include also values for any required properties
MyModel.updateOrCreate({
id: 123
}, console.log);
この機能がサポートされていない場合は、GitHub のコネクタプロジェクトで問題を報告してください。
影響を受けるインスタンスへのアクセス
単一のインスタンスのみに影響を与える操作(PersistedModel.deleteAll
とPersistedModel.updateAll
を除く、すべての作成・取得・更新・削除操作)は通常コンテキストオブジェクトに影響を受けたインスタンスを提供します。
ただし、操作に応じて、このインスタンスは変更可能なctx.instance
または読み取り専用のctx.currentInstance
のいずれかで提供されます。
メソッド | before save | persist | after save | before delete | after delete |
---|---|---|---|---|---|
create |
ctx.instance |
ctx |
ctx.instance |
--- | --- |
findOrCreate |
ctx.instance |
ctx |
ctx.instance |
--- | --- |
updateOrCreate |
n/a* | ctx |
ctx.instance |
--- | --- |
upsertWithWhere |
n/a* | ctx |
ctx.instance |
--- | --- |
updateAll |
n/a | n/a | n/a | --- | --- |
prototype.save |
ctx.instance |
ctx |
ctx.instance |
--- | --- |
prototype |
ctx |
ctx |
ctx.instance |
--- | --- |
prototype.delete |
--- | --- | --- | ctx.where.id |
ctx.where.id |
deleteAll |
--- | --- | --- | n/a | n/a |
replaceOrCreate |
ctx.instance |
ctx |
ctx.instance |
--- | --- |
prototype
|
ctx.instance |
ctx |
ctx.instance |
--- | --- |
(*) 操作 updateOrCreate
とupsertWithWhere
は、 “before save”フックにインスタンスを提供しません。
操作がUPDATEまたはCREATEになるかどうかを事前に伝えることは不可能であるため、既存の「currentInstance」が操作の影響を受けるかどうかを知る方法はありません。
詳細については、次のセクションを参照してください。
フック
LoopBackは、次の操作フックを提供します。
次の表は、PersistedModelメソッドが呼び出すフックの一覧です。
メソッドの名前 | 呼び出されるフック |
---|---|
all |
access, loaded |
create | before save, after save, loaded, persist |
upsert (別名 updateOrCreate) | access, before save, after save, loaded, persist |
upsertWithWhere | access, before save, after save, loaded, persist |
findOrCreate | access, before save*, after save*, loaded, persist |
deleteAll (destroyAll) deleteById (destroyById) |
access, before delete, after delete |
updateAll | access, before save, after save, persist |
prototype.save | before save, after save, persist, loaded |
prototype.delete | before delete, after delete |
prototype. updateAttributes |
before save, after save, loaded, persist |
replaceOrCreate | access, before save, after save, loaded, persist |
prototype. replaceAttributes replaceById |
before save, after save, loaded, persist |
(*) findOrCreate
が既存のモデルを見つけたとき、saveフックは起動されません。
ただし、アトミック実装を提供するコネクタは、モデルが作成されるかどうかを事前に判断できないため、モデルが作成されていなくてもbefore save
を起動するかもしれません。
access
access
フックは、データベースがモデルに照会されるたび、つまり、PersistedModel の
あらゆる 作成・取得・更新・削除メソッドが呼び出されるときに起動します。
オブザーバーは、例えば追加の制限を加えるなどして、クエリを変更することができます。
Note:
データベースからモデルインスタンスを読み込んだメソッドによってaccess
フックが既に起動されているため、プロトタイプメソッドはフックを起動しません。
たとえば、REST APIを使用してプロトタイプメソッドを呼び出すと、静的 findById()
(「access」フックを起動する)とプロトタイプメソッドの2つのモデル呼び出しが要求されます。
コンテキストプロパティ
Model
- 照会されるモデルのコンストラクタquery
-where
・include
・order
フィールドを含むクエリ
例:
MyModel.observe('access', function logQuery(ctx, next) {
console.log('Accessing %s matching %s', ctx.Model.modelName, ctx.query.where);
next();
});
MyModel.observe('access', function limitToTenant(ctx, next) {
ctx.query.where.tenantId = loopback.getCurrentContext().tenantId;
next();
});
before save
before save フックは、モデル・インスタンスが変更(作成・更新)される前に起動されます。具体的には、以下のPersistedModelのメソッドが呼び出されたときです。
- create()
- upsert()
- upsertWithWhere()
- findOrCreate() -
findOrCreate
が既存のモデルを見つけた場合、saveフックを起動しません。ただし、アトミック実装を提供するコネクタは、モデルが作成されなくてもbefore save
フックを起動する可能性があります。これは、モデルが作成されるかどうかを事前に判断できないためです。 - updateAll()
- prototype.save()
- prototype.updateAttributes()
- replaceOrCreate()
- prototype.replaceById() / replaceAttributes()
フックは、 モデル検証 関数が呼び出される 前に 起動されます。
Tip:
before save
フックはバリデータが呼び出される前に起動されるので、空または欠損値が既定値で満たされていることを確認できます。
このフックをトリガするメソッドに応じて、コンテキストは次のプロパティセットのいずれかを持ちます。
- 単一モデルの完全保存
Model
- 保存されるモデルのコンストラクタinstance
- 保存されるモデルのインスタンス。 値はModel
クラスのインスタンスです。
- おそらく複数のモデルの部分的更新
Model
- 保存されるモデルのコンストラクタwhere
- どのインスタンスが影響を受けるかを記述するwhereフィルタdata
- 更新中に適用される(部分的な)データcurrentInstance
- 影響を受けるインスタンス
ctx.isNewInstance
before save フックは、ctx.instance
が設定されている場合に、ctx.isNewInstance
プロパティを次のように提供します。
- すべてのCREATE操作で true です。
- すべてのUPDATEおよびREPLACE操作で false です。
updateOrCreate
・upsertWithWhere
・replaceOrCreate
・prototype.save
・prototype.updateAttributes
・updateAll
操作は undefined です。
埋め込まれた関係
別のモデルに埋め込まれているモデルのbefore saveフックを定義することができます。
そして、包含モデルのインスタンスを更新または作成すると、埋め込まれたモデルの操作フックが起動されます。
この場合、コンテナモデルの新しいインスタンスのみが作成されるため、ctx.isNewInstance
はfalseになります。
たとえば、Customer emdedsOne Address
だとすると、Addressモデルにbefore save
フックを定義し、新しいCustomerのインスタンスを作成すると操作フックが起動されます。
“before save” フックでモデルデータを操作する
上で説明したように、コンテキストは instance
プロパティ、または一組の data
および where
プロパティのいずれかを提供します。
ctx.instance
の完全なモデルインスタンスを公開すると、フックは独自モデルのインスタンスメソッドを呼び出すことができます(たとえば、住所などの注文データが変更されたときに order.recalculateShippingAndTaxes()
を呼び出すことができます)。
LoopBackの作成・取得・更新・削除操作が可能な限りインスタンスを提供するのはこのためです。
インスタンスオブジェクトを提供することが実現できない場合として、2つの特徴的な例外があります。
PersistedModel.updateAll
は与えられた条件に一致する複数のインスタンスを更新します。 LoopBackはデータベースからデータをロードすることさえできません。これらのインスタンスを見つけて必要な変更を適用するのはデータベースの責任です。PersistedModel.updateAttributes
が部分的な更新を実行すると、モデルのプロパティの一部のみが変更されます。 LoopBackには利用可能なモデルインスタンスがありますが、データベース内でどのモデルプロパティを変更すべきかも知る必要があります。 操作対象のデータをctx.data
(変更すべきプロパティだけを含むプレーンオブジェクト)で渡すことで、フックの実装に、変更するプロパティを簡単に追加/削除できるようになります。ctx.currentInstance
を不変(読み取り専用)として扱う限り、変更されるモデルインスタンスにアクセスできます。
例
MyModel.observe('before save', function updateTimestamp(ctx, next) {
if (ctx.instance) {
ctx.instance.updated = new Date();
} else {
ctx.data.updated = new Date();
}
next();
});
MyModel.observe('before save', function computePercentage(ctx, next) {
if (ctx.instance) {
ctx.instance.percentage = 100 * ctx.instance.part / ctx.instance.total;
} else if (ctx.data.part && ctx.data.total) {
ctx.data.percentage = 100 * ctx.data.part / ctx.data.total;
} else if (ctx.data.part || ctx.data.total) {
// エラーを報告するか、欠けているプロパティをDBから読み取る
}
next();
});
不要なプロパティの削除
モデルインスタンス内のプロパティを削除(設定解除)するには、その値を未定義に設定するか、またはプロパティを削除するだけで十分ではありません。
代わりに、インスタンスにはunsetAttribute(name)
を呼び出さなければなりません。ただし、コンテキストにdataプロパティがある場合を処理することを忘れないでください!
dataオブジェクトは単純なオブジェクトなので、削除演算子を使用して通常の方法でプロパティを削除できます。
例:
MyModel.observe('before save', function removeUnwantedField(ctx, next) {
if (ctx.instance) {
ctx.instance.unsetAttribute('unwantedField');
} else {
delete ctx.data.unwantedField;
}
next();
});
これにより、フィールドが完全に削除され、誤ったデータがデータベースに挿入されなくなります。
after save
after save
フックは、モデルの変更がデータソース上で永続化された後に呼び出されます。 具体的には、PersistedModel の以下のメソッドが呼び出されたときです。
- create()
- upsert()
- upsertWithWhere()
- findOrCreate()*
- updateAll()
- prototype.save()
- prototype.updateAttributes()
- prototye.replaceAttributes() / replaceById()
- replaceOrCreate()
(*) findOrCreate
が既存のモデルを見つけると、saveフックは起動されません。
ただし、アトミック実装を提供するコネクタは、モデルが作成されるかどうかを事前に判断できないため、
モデルが作成されていなくてもbefore save
フックを起動する可能性があります。
このフックを起動するメソッドに応じて、コンテキストは次のプロパティセットのいずれかを持ちます。
- 単一のモデルが更新された場合:
Model
- 保存されるモデルのコンストラクタです。-
instance
- 保存されたモデルのインスタンス。 値はModel
クラスのインスタンスであり、データストアによって計算された更新値(たとえば、自動生成ID)を含みます。Note:
after saveフックは、
ctx.instance
に加えられた変更を呼び出し元(RESTクライアント)に返しますが、データベースに保存はしません。
</div>
Model.updateAll
によって複数のモデルの部分的な更新が行われた場合:Model
- 保存されるモデルのコンストラクタです。where
- 問い合わせられたインスタンスを記述するwhereフィルタ。以下の警告を参照。-
data
- 更新中に適用された(部分的な)データ。注:after saveフックで 「where」クエリを使用して、影響を受けるモデルを確実に見つけることはできません。次の呼び出しを考えてみましょう。
MyModel.updateAll({ color: 'yellow' }, { color: 'red' }, cb);
「after save」フックが実行される時点では、
{ color: 'yellow' }
の条件にマッチするレコードは存在しません。
The after save
hook provides the ctx.isNewInstance
property whenever ctx.instance
is set, with the following values:
after save
フックは、ctx.instance
がセットされる時はいつでもctx.isNewInstance
プロパティを以下の値に設定して提供します。
- 全ての CREATE 操作の後なら True
- 全ての UPDATE/REPLACE 操作の後なら false
updateOrCreate
・prototype.save
・prototype.updateAttributes
操作は、新しいインスタンスが作成されたか、既存のインスタンスを更新したかを、コネクタが知らせることが必要。 コネクタがこの情報を提供する場合、ctx.isNewInstance
は true またはfalse。 コネクタがまだこの機能をサポートしていない場合は、値は undefined。
Important:
特定のコネクタのみが、ctx.isNewInstance
をサポートしています。他のコネクタでは未定義です。
ctx.isNewInstance サポートの確認を参照してください。
埋め込まれた関係
別のモデルに埋め込まれているモデルのafter save
フックを定義することができます。
そして、包含モデルのインスタンスを更新または作成すると、埋め込みモデルの操作フックが起動されます。
これが発生する と、コンテナモデルの新しいインスタンスのみが作成されるため、ctx.isNewInstance
はfalseになります。
たとえば、Customer emdedsOne Address
だとすると、Addressモデルにafter save
フックを定義し、
新しいCustomerのインスタンスを作成すると操作フックが起動されます。
例
MyModel.observe('after save', function(ctx, next) {
if (ctx.instance) {
console.log('Saved %s#%s', ctx.Model.modelName, ctx.instance.id);
} else {
console.log('Updated %s matching %j',
ctx.Model.pluralModelName,
ctx.where);
}
next();
});
before delete
before delete
フックは、モデルがデータソースから削除される時に起動されます。具体的には、PersistedModelの以下のメソッドが呼び出されたときです。
destroyAll()
(deleteAll()
と同じ)destroyById()
(deleteById()
と同じ)prototype.destroy()
(prototype.delete()
と同じ)
Important:
before delete
操作フックは、リレーショナルまたはNoSQLデータベースなどのバックエンドのデータストアがこの情報を提供していないため、削除されたモデルインスタンスIDのリストを受け取りません。
ただし、単一のモデルインスタンスを削除する場合 、削除されるインスタンスのid
を含む ctx.where
をフックが受け取ります。
コンテキストプロパティ
Model
- 照会されるモデルのコンストラクタ。where
- どのインスタンスが削除されるかを記述するwhereフィルタ。
例:
MyModel.observe('before delete', function(ctx, next) {
console.log('Going to delete %s matching %j',
ctx.Model.pluralModelName,
ctx.where);
next();
});
なんらかの条件に基づいてモデルの削除を拒否するには、next()
にエラーを渡して、削除操作を中止します。
例:
if (subscriptions.length > 0) {
//Stop the deletion of this Client
var err = new Error("Client has an active subscription, cannot delete");
err.statusCode = 400;
console.log(err.toString());
next(err);
} else {
next();
}
after delete
Important:
after delete
操作フックは、リレーショナルまたはNoSQLデータベースなどのバックエンドのデータストアがこの情報を提供していないため、削除されたモデルインスタンスIDのリストを受け取りません。
ただし、単一のモデルインスタンスを削除する場合 、削除されたインスタンスのid
を含む ctx.where
をフックが受け取ります。
The after delete
hook is triggered after some models are removed from the datasource, specifically when the following methods of PersistedModel are called:
after delete
フックは、モデルがデータソースから削除された時に起動されます。具体的には、PersistedModelの以下のメソッドが呼び出されたときです。
destroyAll()
(deleteAll()
と同じ)destroyById()
(deleteById()
と同じ)prototype.destroy()
(prototype.delete()
と同じ)
コンテキストプロパティ
Model
- 照会されたモデルのコンストラクタwhere
- 削除されたインスタンスを記述するwhereフィルタ
例:
MyModel.observe('after delete', function(ctx, next) {
console.log('Deleted %s matching %j',
ctx.Model.pluralModelName,
ctx.where);
next();
});
loaded
このフックは、PersistedModelの以下のメソッドによって起動されます。
find()
findOne()
findById()
exists()
count()
create()
upsert()
(updateOrCreate()
と同じ)upsertWithWhere()
findOrCreate()
*prototype.save()
prototype.updateAttributes()
replaceOrCreate()
prototype.replaceAttributes()
/replaceById()
Important:
既定では、 create
と updateAttributes
は、 コールバックに返されたモデルインスタンスへデータベースの更新を適用しないので、「loaded」フックによって行われた変更は破棄されます。この動作を変更するには、モデルごとのオプションで updateOnLoad: true
を設定します。
LoopBackは、コネクターがデータを読み取った後で、そのデータからモデルインスタンスを作成する前に、このフックを呼び出します。 これにより、フックはデータを復号化することができます(たとえば)。注:このフックは、フルモデルのインスタンスではなく、未加工のデータベースデータで呼び出されます。
コンテキストプロパティ
data
- コネクタによって返されたデータ(データベースからロードされたもの)
persist
このフックは、データをデータソースに永続化する操作、具体的にはPersistedModelの次のメソッドによって起動されます。
create()
upsert()
(updateOrCreate()
と同じ)upsertWithWhere()
findOrCreate()
*prototype.save()
prototype.updateAttributes()
updateAll()
replaceOrCreate()
prototype.replaceAttributes()
/replaceById()
このフックと「before save 」フックを混同しないでください:
- before save – 保存しようとしているモデルインスタンスを観察して操作するために、このフックを使います。(たとえば、国コードが設定されていて、国名がなく、国名をセットしたい場合)
- persist – データソースに永続化される直前のデータを監視(および操作)するためにこのフックを使います。(たとえば、データベース内の値を暗号化したい場合)
create
の最中、persist
フックで行った変更は、データベースに反映されます。
しかし、create
のコールバックによって得られた instance
への変更は、反映されません。
第2に、アトミックを実装するコネクターでfindOrCreate
は、既存のレコードが後でデータベース内で見つかった場合でも、毎回オブジェクトの新しいインスタンスが作成されます。そのため、
ctx.data.id
とctx.currentInstance.id
は新しいIDがセットされます。ctx.isNewInstance
はtrue
です。
コンテキストプロパティ
data
- コネクタに送信されるデータ(データベースに保存されます)currentInstance
- 影響を受けるモデルインスタンスisNewInstance
- 下記参照
このフックでctx.isNewInstance
は、
- 全ての CREATE 操作において true
- 全ての UPDATE 操作においてい false
- updateOrCreate・upsertWithWhere・replaceOrCreate・prototype.save・prototype.updateAttributes・updateAll 操作については undefined
afterInitialize フック
Important:
afterInitialize
は厳密には操作フックではありません。実際には非推奨ではない唯一のモデルフックです。
これは同期メソッドであり、コールバック関数は使用しません。フックでロジックを実行した後に next() を呼び出す必要はありません。
このフックは、モデルが初期化された後に呼び出されます。
例
/common/models/coffee-shop.js
...
CoffeeShop.afterInitialize = function() {
//your logic goes here
};
...
ほとんどの操作は、実際にアクションを実行する前に、モデルを初期化する必要があるが、initializeイベントが起動されていないいくつかの例がある。
例えば、exists
, count
や一括更新のRESTエンドポイントへのHTTPリクエストなどである。
モデルフックからの移行
次の表は、推奨されていない各モデルフックに代わって使用する新しいフックを示しています。
モデルフック | 代替となる操作フック |
---|---|
beforeValidate | before save |
afterValidate | persist |
beforeCreate | before save |
afterCreate | after save |
beforeSave | before save |
afterSave | after save |
beforeUpdate | before save |
afterUpdate | after save |
beforeDestroy | before delete |
afterDestroy | after delete |