Sails.jsのHookについて説明するよ

# :question: Hookとは？
Sails.jsのアプリケーションの動きに変更を加えるための仕組みです。アクションの追加からルートの追加まで結構なんでもできます。というかそもそもSails.jsの大体の仕組みがフックを使って実装されているようです。

**:warning: 以降、Hookはフックと表記します。**
**:warning: Sails.js 1.0以降を想定しています。**

# :seedling: フックの種類

## コアフック
Sails.jsのチームによってメンテナンスされているフック集。gruntのアセットパイプラインや、Waterline ORMライブラリのサポートなどSails.jsの根幹を構成するフックがあります。

## インストールして利用するフック
npm経由でインストールすることで利用できるフックです。200以上あるようですが、下記に有用そうなものをリストアップしておきます。

- [sails-hook-cron](https://www.npmjs.com/package/sails-hook-cron)
- [sails-hook-babel](https://www.npmjs.com/package/sails-hook-babel)
- [sails-hook-autoreload](https://www.npmjs.com/package/sails-hook-autoreload)

フックが出来ることの範囲の広さがよくわかりますね。

## プロジェクト固有のフック
Sails.jsのプロジェクトごとに作成するフックです。`api/hooks`以下に作成するフックです。基本的にプロジェクト固有のフックを作成することになります。

# フックの作り方
フックの作り方は非常に簡単です。

1. `api/hooks`以下にディレクトリを作成する
2. `index.js`を以下の内容で作成します。この関数が返すオブジェクトが、フック本体となります。

```js:api/hooks/myhook/index.js
module.exports = function myhook(sails) {
  return {};
}
```

## フックが持つプロパティ/メソッド
フックはいくつかのプロパティやメソッドを持つことができます。（Sails.jsのドキュメントではfeatureと読んでいるようです）
ドキュメントでは以下の5つがリストアップされています。以降これらをまとめてフックの属性と呼びます。

- defaults
- configure()
- initialize(cb)
- routes
- registerActions(cb)

順に説明していきます。

### :gear: defaults
フックで利用するプロパティのデフォルト設定を記述するための属性です。
オブジェクトもしくは関数として設定できます。defaultsで設定したオブジェクトは`sails.config.<key>`で参照できます。勝手にSails.jsが名前空間を`sails.config`に切ってくれないようなので注意しましょう。（うっかり他のフックが設定しているプロパティを上書きしないように）

```js:api/hooks/myhook/index.js
module.exports = function myhook(sails) {
  return {
    defaults: {
      myhook:  {
        name: 'myhook'
      }
    }
  };
};
```

このようなフックを作成すると、`sails.config.myhook.name`に`myhook`という文字列が設定されます。

```js:config/bootstrap.js
module.exports.bootstrap = function(done) {

  sails.inspect = require('util').inspect;

  if (typeof sails.config.myhook === 'undefined') {
    sails.log('sails.config.myhook is not defined.');
  } else {
    sails.log(`sails.config.myhook is "${sails.inspect(sails.config.myhook)}"`); // -> debug: sails.config.myhook is "{ name: 'myhook' }"
  }

  return done();
};
```

### :gear: configure()
全てのフックのdefaults属性が読み込まれた後に、実装されるメソッドです。フックの設定を環境に応じて設定するために利用します。
configure()メソッドは引数もとらずまた何も値を返しません。configure()メソッドの便利な点は、全てのフックが読み込まれた後に、またinitialize()メソッドが呼ばれる前に確実に呼ばれることです。

### :gear: initialize(cb)
フックの初期化メソッドです。普通の処理に加えて、非同期な初期化処理、もしくは他のフックに依存した初期化処理を記述できます。
例えば、ORMのフックが初期化された後にモデルに独自の処理を追加するなどのことを記述できます。

initialize(cb)の引数に渡されるcbはコールバックであり、初期化が完了した後に呼び出す必要があります。ちなみにcbの呼び出しまでにかけて良い時間は最大10秒なので、10秒以内に基本初期化処理を完了させる必要があります。（defaultsに`_hookTimeout`というプロパティを設定することで時間を伸ばすことは可能です）

cbが実行されると、フックに対応した`sails.on(eventName,...)`で待ち受けることができるイベントが発行されます。このイベントを使って他のフックが初期化処理の完了を待つわけですね。

フック完了時に発行されるイベントは、`hook:<hook identity>:loaded`の形になります。
例えばORMのフックであれば、`hook:orm:loaded`、npm経由でインストールされたフック`node_modules/sails-hoge-hook`であれば`hook:hoge:loaded`になります。他には、

- `node_mudles/pogehook`であれば`hook:pogehook:loaded`
- プロジェクト固有のフック`api/hooks/bogehook`であれば`hook:bogehook:loaded`

という感じになります。

### :gear: routes
カスタムルーティングを定義するための属性です。フックが独自に設定したいルーティングを設定する時に使います。
routesはオブジェクトとして定義し、`before`キーと`after`キーを持ちます。

- `before`キーに指定されたルーティング
    - 他のルーティング(Sails.jsのブループリントルーティングや、ユーザーの設定するルーティング)が設定される前に、ルーティングの紐付けが行われます
- `after`キーに指定されたルーティング
    - `before`の反対の挙動で、全てのルーティングが設定された後に紐付けが行われます

```js:api/hooks/myhook/index.js
module.exports = function(sails) {
  routes: {
    before: {
      'GET /myhook/before': function(req, res, next) {
        return res.json({message: 'Before routes'});
      },
    },
    after: {
      'GET /myhook/after': function(req, res, next) {
        return res.json({message: 'After routes'});
      },
    },
  },
};
```

### :gear: registerActions(cb)
アクション(Sails.jsではリクエストに応答する責務を持つオブジェクトとして定義されています)を登録できます。ここで登録したアクションをルーティングで実行することができます。
アクションの登録が完了したら、引数のcbを実行し終了を知らせる必要があります。Sails.jsのドキュメントを読む限り、このregisterActionsはinitializeから呼び出しても問題ないらしいです。

# フックの細々としたこと

## Sails.jsが発行するイベントについて
`sails.on(evnetName, eventHandlerFn)`もしくは`sails.after(eventName, eventHandlerFn)`で待ち受けることが出来るイベントがあります。これらのイベントの発行に合わせてフックを実行することで細かくSails.jsの挙動に変更を加えることができます。

|イベント名|いつ発行されるか？|
|:------|:--------------|
|`ready`|アプリがロードされ、ブートストラップが完了した後に発行されます。**リクエストは受け付ける前です。**|
|`lifted`|アプリの全ての初期化が完了し、**リクエストを受け付けるようになった後**に発行されます。|
|`lower`|アプリが終了し、リクエストの受付が終了した後に発行されます。|
|`hook:<hook identity>:loaded`|フックの初期化が完了したときに発行されます。|