モデルのバリデーションエラーはカスタムイベントで通知すると便利

[Backbone.js Tips And Patterns | Smashing Coding](http://coding.smashingmagazine.com/2013/08/09/backbone-js-tips-patterns/) を読みました。

ほとんどは特に真新しくもない当たり前な内容なんですが、 "BROADCAST CUSTOM ERROR EVENT" はいいなと思ったので紹介します。

このパターンは Model で複数の属性のバリデーションを行いたい場面で使うものです。

# RETURN AN ERROR OBJECT

まず、普通な `validate` の実装です。

```js:Model
validate: function (attrs) {
  var errors = [];
  if (_.isUndefined(attrs.name)) {
    errors.push({
      attr: 'name',
      message: 'Name is required'
    });
  }
  if (errors.length) return errors;
}
```

まず Backbone の基本事項として下記を再確認してください:

1. `validate` メソッドが真を返すと `invalid` イベントが発生する
2. 返り値が `invalid` イベントのコールバックに渡される

で、上記のコードサンプルは `errors` という配列を作っています。なんでわざわざこんなことをしているのかというと、コールバックに複数の属性の情報を渡すには配列などを利用するしか無いわけです。そして View のコールバックでその配列の値をパースします。

このパターンは `validate` メソッドで複数の属性を処理するときによく使われます。前述のサイトでは "RETURN AN ERROR OBJECT" という名前で紹介されています。

この方法で一応複数属性のバリデーションはできるんですが、レンダラが肥大化してしてしまいがち、という問題があります。

```js:View
initialize: function () {
  this.listenTo(this.model, 'invalid', this.renderMessages);
},
renderMessages: function (errors) {
  _.each(errors, function (error) {
    if (error.attr === 'name') {
      this.$('.name-message').html(error.message).show();
    }
  }, this);
}
```

ここでは `renderMessages` メソッドを定義しています。このメソッドでやるのがエラーメッセージをまとめてリストにして表示する、とかなら単純で大きな問題にはなりませんが、ここでやっているように属性毎にレンダリングする方法を変えたい、とかだと属性が増える度にどんどん肥大化していきます。

そのような問題に対して、個々の属性毎にレンダラを定義して、下記のようにリファクタリングすることもよく行います。

```js:View
renderMessages: function (errors) {
  _.each(errors, function (error) {
    if (error.attr === 'name') {
      this.renderNameMessage(error.message);
    }
  }, this);
},
renderNameMessage: function (message) {
  this.$('.name-message').html(message).show();
}
```

# BROADCAST CUSTOM ERROR EVENT

この個別レンダラに対応するカスタムイベントを発行するのが "BROADCAST CUSTOM ERROR EVENT" です。

```js:Model
validate: function (attrs) {
  if (_.isUndefined(attrs.name)) {
    this.trigger('invalid:name', 'Name is required');
  }
  // 注意：バグがある。詳しくは後述
}
```

このように `invalid:ATTRIBUTE` となるようなイベントを発行します。対応する View では個別レンダラをコールバックに設定します。

```js:View
initialize: function () {
  this.listenTo(this.model, 'invalid:name', this.renderNameMessage);
}
```

この方法のよいところは、 View で Model の `validate` メソッドの返り値の実装を知らなくてもよい点です。例えば今回の場合でいえば、 `attr` と `message` というプロパティを持つオブジェクトの配列が返される、ということを View が知っていなければなりません。しかし、カスタムイベントでメッセージを渡す方式であれば、属性名の種類さえ知っていればよいので View と Model の結合度はより小さくなります。

# ２つを組み合わせるのがオススメ

ただ、引用しているブログ記事には重大なバグがあって、それは何かというと、 `validate` メソッドが何も返さないのでバリデーションに通ったとみなされてしまう、という点です。なので一つでもバリデーションに失敗したら `true` を返すようにする、などの修正が必要です。

ですが、オススメしたいのは "BROADCAST CUSTOM ERROR EVENT" と "RETURN AN ERROR OBJECT" を組み合わせる方式です。

```js:Model
validate: function () {
  var errors = [];
  if (_.isUndefined(attrs.name)) {
    errors.push({
      attr: 'name',
      message: 'Name is required'
    });
    this.trigger('invalid:name', 'Name is required');
  }
  if (errors.length) return errors;
}
```

何故組み合わせるべきだと思うのか。それは `change` と `change:ATTRIBUTE` イベントの構造と似せることが可能だからです。

`change` と `change:ATTRIBUTE` イベントは属性が変更されたときに発生します。複数の属性が変更されたとき、属性毎に `change:ATTRIBUTE` イベントが発生した後に `change` イベントが発生します。個別対応したければ `change:ATTRIBUTE` を監視し、一括で問題無いなら `change` を使う、という使い分けがされます。

それと同じことが `invalid` と `invalid:ATTRIBUTE` でできるようになるわけです。インタフェースが似ているので使い勝手がよさそうです。