Backbone.js入門 「Model」

### 閲覧上の注意

この記事で対象としているバージョン0.5.3は結構古いので注意してください。例えばこの記事の内容でいうと、`validate`に失敗したときに発生するイベントは`'error'`から`'invalid'`に変更されています。

その他の割りと新しい情報は [Backbone.js Advent Calendar 2012](http://www.adventar.org/calendars/15) などにあります。

（追記ここまで）

----

今日のテーマは `Model` です。
MVC に関する回で *Backbone.js における MVC は Rails などの WAF における MVC よりは伝統的な MVC に近い* というようなことを書きました。
しかしながら、モデルに関して言えばどちらの場合も大差ありませんから、気分が楽です。
Rails におけるモデルである ActiveRecord が OR マッパーとしての役割を持っていたり、データのバリデーション機能を持っていたりと、単なるデータを表す以上の機能を備えているように、Backbone.js における `Model` もサーバと通信してデータを取得・永続化させたり、バリデーションを行うなどの機能を持っています。
今回は *データの設定* に加え、この *データの取得・永続化* と *データのバリデーション* に焦点を当てて `Model` の使い方を解説したいと思います。

#基本的な使い方
`View` の回でも述べたように、`Model.extend` を使います。

```js
var Blog = Backbone.Model.extend({
    defaults: {
        "dateTime": new Date().toISOString()
    },
    initialize: function (attrs, options) {
    },
    validate: function (attrs) {
        if (attrs.text.length === 0) {
            return "本文が入力されていません";
        }
    }
});
```

##initialize([attributes][, options])

インスタンスの初期化時に呼び出されます。

```js
var Example = Backbone.Model.extend({
    initialize: function (attrs, options) {
        console.log("attrs", attrs);
        console.log("options", options);
    }
});
var e = new Example({a: 1}, {b: 2});
// => "attrs" {a: 1}
// => "options" {b: 2}
e.has("a") // => true
e.get("a") // => 1
e.has("b") // => false
e.get("b") // => undefined
```

`has` や `get` は後述。
ここのポイントは、`attrs` はモデルの値として設定されるが、`options` はそうではない、ということです。なので、例えばブログの本文や執筆時刻などのように **データベースに保存する情報** は `attrs` で渡し、**データベースには保存しない設定項目** を渡す場合は `options` を使うようにするとよいでしょう。

#データの設定

モデルにデータを設定したり、それを取得、変更したりする方法です。

##get(attrName)

モデルの属名を取得します。これは問題ないですよね。

##escape(attrName)

`get` と同じですが、XSS などの危険がある箇所ではこちらを使いましょう。

```js
var blog = new Blog({text: "<script>alert('xss')</script>"});
blog.get('text'); // => <script>alert('xss')</script>
blog.escape('text'); // => &lt;script&gt;alert(&#x27;xss&#x27;)&lt;&#x2F;script&gt;
```

##has(attrName)

モデルに属性が設定されているかどうか。返り値は Boolean です。

##set(attrs, [options])

属性を設定します。この時、change:attrName イベントが発生します。

```js
blog.bind("change", function () {console.log("change")});
blog.bind("change:text", function () {console.log("change:text")});
blog.set({text: "foobar"}); // change:text と change 両方が出力される
blog.get("text"); // => foobar
```

イベントを発生させたくない場合は silent オプションを使います。

```js
blog.set({text: "hoge"}, {silent: true}); // 何も出力されない
blog.get("text"); // => hoge
```

注意点は、silent を使うと後述する `validate` を用いたバリデーションも **実行されない** 、という点です。

##unset(attrName, [options])

`attrName` の属性を削除して change:attrName イベントを発生させます。こちらもオプションで silent を指定することで抑制できます。

##clear(options)

全ての属性を削除して change イベントを発生させます。こちらもオプションで silent を指定することで抑制できます。

##previous(attrName)

ここまででモデルの属性まわりのメソッドの説明を行いましたが、その中で change イベントを発生させるものがいくつかありました。
この change イベントに `bind` されたコールバック関数内でのみ意味を持つのが `previous` です。
名前の通り、変更される前の値を取得することができます。

```js
var Blog = Backbone.Model.extend({
    initialize: function (attrs, options) {
        this.bind("change:text", function (model, val) {
            console.log(model.previous("text"), val);
        });
    }
});
var blog = Blog({text: "sample"});
blog.set({text: "example"}); // => sample example
```

前回の値を使ったバリデーションは `validate` で行うことができるので、こちらは主にログ管理などに使うことになるでしょう。

##change([options])

小ネタになるのですが、例えば for ループの中で `set` を複数回に分けて呼び出して、少しずつ属性を設定するような場面があったら、毎回 change を発生させるのではなく、最後に一回だけ発生させたくなるでしょう。その時、最後の `set` でだけ silent を `false` にするのでもいいですが、`change` を使うと簡単にできます。

```js
// 何かの事情で model.set({a: 1, b: 1, c: 1, d: 1}) と書けない
model.set({a: 1}, {silent: true});
model.set({b: 1}, {silent: true});
model.set({c: 1}, {silent: true});
model.set({d: 1}, {silent: true});
model.change(); // 最後に一回だけ change イベントを発生
```

`change` の代わりに `trigger('change')` でいいのではないかと思うかも知れませんが、ダメです。理由はソース参照

#データの取得と永続化

WEB サーバやブラウザの localStorage からデータを取得してモデルを作ったり、逆にそれらにモデルを保存する方法です。

##id

モデルの主キー。主キーとわざわざ読んでいるのは、この値を使ってアクセスする API の URI を変更し、通常用いられる RESTful API では、この値がデータベース中での主キーと対応する為です。

##urlRoot

次の `url` 中で用いられる。

##url()

モデルのリソースの URI を返す。`id` と `urlRoot` を組み合わせて、`/urlRoot/id` とするのがデフォルト。

```js
var Blog = Backbone.Model.extend({
    urlRoot: '/api/blogs'
});
var blog = new Blog({id: 1});
blog.url() // => /api/blogs/1
```

実際に `url` 自体を直接利用する機会は無いと思います。これは、後述する `fetch` `save` `destroy` の中で利用されます。

##fetch([options])

`model.url()` にアクセスして帰ってくる JSON を使ってモデルを更新します。例えば、次のようなモデルオブジェクトがあるとします。

```js
blog.url() // => /api/blogs/1
blog.get('text') // => undefined
```

そして、`/api/blogs/1` にアクセスすると次のような JSON が返ってくる場合

```js
{"text": "foobar", "datetime": "December 14, 2011 01:36:00"}
```

`fetch` を実行すると次のようになります。

```js
blog.fetch()
blog.get('text') // => foobar
blog.get('datetime') // => December 14, 2011 01:36:00
```

`options` の値が内部では `jQuery.ajax` に渡されるので、`/api/blogs/1?foo=bar` のようにクエリ文字を渡したい場合は次のように書きます。

```js
blog.fetch({data: {foo: "bar"}});
```

また、`fetch` した時は change イベントが発生します。抑制するには `options` に silent です。

##parse(response)

さて先ほどの例だと datetime という属性も作られますが、現状では文字列のままです。`Date` 型のオブジェクトにしたい場合のように、サーバからの返り値を属性に設定するまえに処理したい場合には `parse` を使いましょう。

```js
var Blog = Backbone.Model.extend({
    urlRoot: "/api/blogs",
    parse: function (response) {
        response.datetime = new Date(response.datetime);
        return resopnse;
    }
});
blog = new Blog({id: 1});
blog.fetch();
blog.get("datetime"); // => Date オブジェクト
```

##save([attrs], [options])、destroy([options])

`url` で差される先にデータを保存及び削除します。当然ですが、サーバが RESTful インタフェースを実装している必要があります。Rails で実装する方法については [RailsアプリでBackbone.jsを使う](http://qiita.com/items/1294) で取り上げました。

#データのバリデーション

##validate(attributes)

`set` と `save` で silent が false であるときに実行されます。

```js
var Blog = Backbone.Model.extend({
    initialize: function (attrs, options) {
    },
    validate: function (attrs) {
        if (attrs.text.length === 0) {
            return "本文が入力されていません";
        }
    }
});
```

返り値が会った場合、error イベントが発生します。
これを捕捉するため、`bind` を使ってもよいですが、特定の状況下でのみ処理を行いたいような場合は、次のように `options` として error ハンドラを渡すと簡単です。

```js
blog.bind("error", function (model, error) {
    console.log(error);
});
blog.set({text: ""}) // => 本文が入力されていません

blog.set({text: ""}, {
    error: function (model, error) {
        console.log('hoge');
    }
}); // => hoge
```

このように、`options` で error ハンドラが渡された場合、`trigger('error')` は実行されないので注意が必要です。

#まとめ

基本的な Model の使い方を見てきました。
次回は、前回取り上げた `View` と `Model` の連携に関する話題です。
それではまた明日。

---

1. [Backbone.js入門 Events](http://qiita.com/items/16b799d0ec0a0ae3f78e)
2. [Backbone.js入門 MVC](http://qiita.com/items/03f49c64d6c4bacdbb26)
3. [Backbone.js入門 View](http://qiita.com/items/8ee2ed1949cd7c337f06)
4. Backbone.js入門 Model
5. [Backbone.js入門 ViewとModelの連携](http://qiita.com/items/7ce4b1ceb5d18c9cef08)
6. [Backbone.js入門 Collection](http://qiita.com/items/c19d2fff0dc23e4015ff)
7. [Backbone.js入門 ViewとModelとCollectionの連携](http://qiita.com/items/7095a32cc5e438172844)
8. [Backbone.js入門 RouterとHistory](http://qiita.com/items/13f3d1f71d31f3e78123)

- [BackboneJS 日本語メーリングリスト](https://groups.google.com/forum/?fromgroups#!forum/backbonejs-ja)