Edited at

Backbone.js入門 「Model」

More than 5 years have passed since last update.


閲覧上の注意

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

その他の割りと新しい情報は Backbone.js Advent Calendar 2012 などにあります。

(追記ここまで)


今日のテーマは Model です。

MVC に関する回で Backbone.js における MVC は Rails などの WAF における MVC よりは伝統的な MVC に近い というようなことを書きました。

しかしながら、モデルに関して言えばどちらの場合も大差ありませんから、気分が楽です。

Rails におけるモデルである ActiveRecord が OR マッパーとしての役割を持っていたり、データのバリデーション機能を持っていたりと、単なるデータを表す以上の機能を備えているように、Backbone.js における Model もサーバと通信してデータを取得・永続化させたり、バリデーションを行うなどの機能を持っています。

今回は データの設定 に加え、この データの取得・永続化データのバリデーション に焦点を当てて Model の使い方を解説したいと思います。


基本的な使い方

View の回でも述べたように、Model.extend を使います。

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])

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

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

hasget は後述。

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


データの設定

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


get(attrName)

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


escape(attrName)

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

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 イベントが発生します。

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 オプションを使います。

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 です。

名前の通り、変更される前の値を取得することができます。

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 を使うと簡単にできます。

// 何かの事情で 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 を返す。idurlRoot を組み合わせて、/urlRoot/id とするのがデフォルト。

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 を使ってモデルを更新します。例えば、次のようなモデルオブジェクトがあるとします。

blog.url() // => /api/blogs/1

blog.get('text') // => undefined

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

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

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

blog.fetch()

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

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

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

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


parse(response)

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

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を使う で取り上げました。


データのバリデーション


validate(attributes)

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

var Blog = Backbone.Model.extend({

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

返り値が会った場合、error イベントが発生します。

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

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 の使い方を見てきました。

次回は、前回取り上げた ViewModel の連携に関する話題です。

それではまた明日。



  1. Backbone.js入門 Events

  2. Backbone.js入門 MVC

  3. Backbone.js入門 View

  4. Backbone.js入門 Model

  5. Backbone.js入門 ViewとModelの連携

  6. Backbone.js入門 Collection

  7. Backbone.js入門 ViewとModelとCollectionの連携

  8. Backbone.js入門 RouterとHistory