操作フックは、作成・読取・更新・削除に関する高レベルの操作を実行するすべてのメソッドによって起動されます。LoopBackバージョン3.0の操作フックには多くの変更が加えられました。
Page Contents

概要

操作フック は特定のメソッドに束縛されず、作成・読取・更新・削除に関する高レベルの操作を実行するすべてのメソッドから起動されます。

対象は、アプリケーションモデルが継承する 永続化モデル のすべてのメソッドです。 操作フックを使用すると、それらの操作を呼び出す特定のメソッド(たとえばcreatesaveupdateOrCreate)に関係なく、データを変更するアクションを割り込ませることができます。

APIは単純です。メソッドModel.observe(name, observer)で、name は操作フックの名前の文字列、例えば「before save」など、そして、 observerfunction 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.beforeRemoteModel.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.optionsMyModel.find()MyModel.create()のようなPersistedModelのメソッドが提供するoptions引数を使用して設定されます。 options引数が指定されなかった場合は、ctx.options は空のオブジェクトに設定されるため、フックは ctx.options が設定されているかどうかを確認する必要がありません。

フックと操作固有のプロパティ

上記の共通のプロパティに加えて、各フックは、操作の影響を受けるモデルインスタンスと適用された変更を識別する追加のプロパティを提供します。 一般的な規則は、コンテキストがinstanceプロパティまたは一組のdatawhereプロパティのいずれかを提供します。

instance

このプロパティは、操作が単一のインスタンスに影響を与え、かつ すべてのモデルプロパティについて完全な更新・作成・削除が実行された時に、提供されます。 例えば、PersistedModel.create()などです。

where + data

操作が複数のインスタンスに影響を与える場合(例えば、PersistedModel.updateAll()または モデルプロパティの一部に部分的な更新が行われる場合(例えば、PersistedModel.prototype.updateAttributes())、コンテキストは、影響を受けたレコードを見つけるために使われるwhere filterと、 なされるべき変更を含むdataオブジェクトを提供する。

isNewInstance

いくつかの操作は、CREATE操作とUPDATE操作を区別するためのフラグを提供します。詳細については、個々のフックのドキュメントを参照してください。

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.deleteAllPersistedModel.updateAllを除く、すべての作成・取得・更新・削除操作)は通常コンテキストオブジェクトに影響を受けたインスタンスを提供します。 ただし、操作に応じて、このインスタンスは変更可能なctx.instanceまたは読み取り専用のctx.currentInstanceのいずれかで提供されます。

メソッド before save persist after save before delete after delete
create ctx.instance ctx
.currentInstance
ctx.instance --- ---
findOrCreate ctx.instance ctx
.currentInstance
ctx.instance --- ---
updateOrCreate n/a* ctx
.currentInstance
ctx.instance --- ---
upsertWithWhere n/a* ctx
.currentInstance
ctx.instance --- ---
updateAll n/a n/a n/a --- ---
prototype.save ctx.instance ctx
.currentInstance
ctx.instance --- ---
prototype
.updateAttributes
ctx
.currentInstance
ctx
.currentInstance
ctx.instance --- ---
prototype.delete --- --- --- ctx.where.id ctx.where.id
deleteAll --- --- --- n/a n/a
replaceOrCreate ctx.instance ctx
.currentInstance
ctx.instance --- ---
prototype
.replaceAttributes
replaceById
ctx.instance ctx
.currentInstance
ctx.instance --- ---

(*) 操作 updateOrCreateupsertWithWhereは、 “before save”フックにインスタンスを提供しません。 操作がUPDATEまたはCREATEになるかどうかを事前に伝えることは不可能であるため、既存の「currentInstance」が操作の影響を受けるかどうかを知る方法はありません。

詳細については、次のセクションを参照してください。

フック

LoopBackは、次の操作フックを提供します。

次の表は、PersistedModelメソッドが呼び出すフックの一覧です。

メソッドの名前 呼び出されるフック

all
find
findOne
findById
exists
count

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あらゆる 作成・取得・更新・削除メソッドが呼び出されるときに起動します。 オブザーバーは、例えば追加の制限を加えるなどして、クエリを変更することができます。

コンテキストプロパティ

  • Model - 照会されるモデルのコンストラクタ
  • query - whereincludeorderフィールドを含むクエリ

例:

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のメソッドが呼び出されたときです。

フックは、 モデル検証 関数が呼び出される 前に 起動されます。

このフックをトリガするメソッドに応じて、コンテキストは次のプロパティセットのいずれかを持ちます。

  • 単一モデルの完全保存
    • Model - 保存されるモデルのコンストラクタ
    • instance - 保存されるモデルのインスタンス。 値はModelクラスのインスタンスです。
  • おそらく複数のモデルの部分的更新
    • Model - 保存されるモデルのコンストラクタ
    • where - どのインスタンスが影響を受けるかを記述するwhereフィルタ
    • data - 更新中に適用される(部分的な)データ
    • currentInstance - 影響を受けるインスタンス

ctx.isNewInstance

before save フックは、ctx.instance が設定されている場合に、ctx.isNewInstance プロパティを次のように提供します。

  • すべてのCREATE操作で true です。
  • すべてのUPDATEおよびREPLACE操作で false です。
  • updateOrCreateupsertWithWherereplaceOrCreateprototype.saveprototype.updateAttributesupdateAll 操作は undefined です。

埋め込まれた関係

別のモデルに埋め込まれているモデルのbefore saveフックを定義することができます。 そして、包含モデルのインスタンスを更新または作成すると、埋め込まれたモデルの操作フックが起動されます。 この場合、コンテナモデルの新しいインスタンスのみが作成されるため、ctx.isNewInstance はfalseになります。

たとえば、Customer emdedsOne Address だとすると、Addressモデルにbefore saveフックを定義し、新しいCustomerのインスタンスを作成すると操作フックが起動されます。

“before save” フックでモデルデータを操作する

上で説明したように、コンテキストは instance プロパティ、または一組の data および where プロパティのいずれかを提供します。 ctx.instanceの完全なモデルインスタンスを公開すると、フックは独自モデルのインスタンスメソッドを呼び出すことができます(たとえば、住所などの注文データが変更されたときに order.recalculateShippingAndTaxes() を呼び出すことができます)。 LoopBackの作成・取得・更新・削除操作が可能な限りインスタンスを提供するのはこのためです。

インスタンスオブジェクトを提供することが実現できない場合として、2つの特徴的な例外があります。

  1. PersistedModel.updateAll は与えられた条件に一致する複数のインスタンスを更新します。 LoopBackはデータベースからデータをロードすることさえできません。これらのインスタンスを見つけて必要な変更を適用するのはデータベースの責任です。
  2. 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 の以下のメソッドが呼び出されたときです。

(*) findOrCreate が既存のモデルを見つけると、saveフックは起動されません。 ただし、アトミック実装を提供するコネクタは、モデルが作成されるかどうかを事前に判断できないため、 モデルが作成されていなくてもbefore saveフックを起動する可能性があります。

このフックを起動するメソッドに応じて、コンテキストは次のプロパティセットのいずれかを持ちます。

  • 単一のモデルが更新された場合:
    • Model - 保存されるモデルのコンストラクタです。
    • instance - 保存されたモデルのインスタンス。 値はModelクラスのインスタンスであり、データストアによって計算された更新値(たとえば、自動生成ID)を含みます。

  • 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
  • updateOrCreateprototype.saveprototype.updateAttributes操作は、新しいインスタンスが作成されたか、既存のインスタンスを更新したかを、コネクタが知らせることが必要。 コネクタがこの情報を提供する場合、ctx.isNewInstanceは true またはfalse。 コネクタがまだこの機能をサポートしていない場合は、値は undefined。

埋め込まれた関係

別のモデルに埋め込まれているモデルの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の以下のメソッドが呼び出されたときです。

コンテキストプロパティ

  • 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

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の以下のメソッドが呼び出されたときです。

コンテキストプロパティ

  • Model - 照会されたモデルのコンストラクタ
  • where - 削除されたインスタンスを記述するwhereフィルタ

例:

MyModel.observe('after delete', function(ctx, next) {
  console.log('Deleted %s matching %j',
    ctx.Model.pluralModelName,
    ctx.where);
  next();
});

loaded

このフックは、PersistedModelの以下のメソッドによって起動されます。

LoopBackは、コネクターがデータを読み取った後で、そのデータからモデルインスタンスを作成する前に、このフックを呼び出します。 これにより、フックはデータを復号化することができます(たとえば)。注:このフックは、フルモデルのインスタンスではなく、未加工のデータベースデータで呼び出されます。

コンテキストプロパティ

  • data - コネクタによって返されたデータ(データベースからロードされたもの)

persist

このフックは、データをデータソースに永続化する操作、具体的にはPersistedModelの次のメソッドによって起動されます。

このフックと「before save 」フックを混同しないでください:

  • before save – 保存しようとしているモデルインスタンスを観察して操作するために、このフックを使います。(たとえば、国コードが設定されていて、国名がなく、国名をセットしたい場合)
  • persist – データソースに永続化される直前のデータを監視(および操​​作)するためにこのフックを使います。(たとえば、データベース内の値を暗号化したい場合)

create の最中、persist フックで行った変更は、データベースに反映されます。 しかし、createのコールバックによって得られた instance への変更は、反映されません。

第2に、アトミックを実装するコネクターでfindOrCreateは、既存のレコードが後でデータベース内で見つかった場合でも、毎回オブジェクトの新しいインスタンスが作成されます。そのため、

コンテキストプロパティ

  • data - コネクタに送信されるデータ(データベースに保存されます)
  • currentInstance - 影響を受けるモデルインスタンス
  • isNewInstance - 下記参照

このフックでctx.isNewInstanceは、

  • 全ての CREATE 操作において true
  • 全ての UPDATE 操作においてい false
  • updateOrCreate・upsertWithWhere・replaceOrCreate・prototype.save・prototype.updateAttributes・updateAll 操作については undefined

afterInitialize フック

このフックは、モデルが初期化された後に呼び出されます。

/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