データストア
2015/06/22
前回は、Cape.JS コンポーネントの中から jQuery の ajax
メソッドを呼んでデータベースを更新する方法について解説しました。
今回のテーマは、Cape.JS のデータストアです。Cape.JS で Web 開発を行う上で非常に重要な概念です。
次に示すのは todo_list.es6
のソースコードの全体です。
class TodoList extends Cape.Component {
init() {
$.ajax({
type: 'GET',
url: '/api/tasks'
}).done(data => {
this.tasks = data;
this.refresh();
});
}
render(m) {
m.ul(m => {
this.tasks.forEach(task => {
m.li(m => {
m.class({ completed: task.done });
m.label(m => {
m.onclick(e => this.toggleTask(task));
m.input({ type: 'checkbox', checked: task.done }).sp();
m.span(task.title);
});
});
});
});
}
toggleTask(task) {
$.ajax({
type: 'PATCH',
url: '/api/tasks/' + task.id,
data: { task: { done: !task.done } }
}).done(data => {
if (data === 'OK') {
task.done = !task.done;
this.refresh();
}
});
}
}
まだ完成には程遠いのですが、すでに39行もあります。少しソースコードを分割したいところです。
分離するとすればどの部分でしょうか。私なら init
メソッドと toggleTask
メソッドにある $.ajax
メソッドの呼び出しを他のクラスに移します。Cape.JS のコンポーネントは MVC モデルの「V」すなわちビューに当たるものなので、データの取得や更新に関わるコードを持たない方がいいのです。
ここで登場するのがデータストアです。MVC モデルの「M」に当たります。
では、app/assets/javascripts
ディレクトリに新規ファイル task_store.es6
を次のような内容で作成してください。
class TaskStore extends Cape.DataStore {
constructor() {
super();
this.tasks = [];
}
refresh() {
$.ajax({
type: 'GET',
url: '/api/tasks'
}).done(data => {
this.tasks = data;
this.propagate();
});
}
toggleTask(task) {
$.ajax({
type: 'PATCH',
url: '/api/tasks/' + task.id,
data: { task: { done: !task.done } }
}).done(data => {
if (data === 'OK') {
task.done = !task.done;
this.propagate();
}
});
}
}
データストアとは、その名の通り、データの格納庫です。Cape.DataStore
クラスを継承して定義してください。データストアがインスタンス化される際に呼ばれる constructor()
メソッドの中では、必ず super()
メソッドを呼んでから、データの初期化を行ってください。
TaskStore
クラスの refresh()
メソッドは、TodoList
クラスの init()
メソッドの内容と同じです。ただし、1箇所だけ違いがあります。this.refresh()
が this.propagate()
に変わっています。
また、TaskStore
クラスの toggleTask()
メソッドは、TodoList
クラスの同名メソッドのコピーです。ただし、this.refresh()
が this.propagate()
に変わっています。
データストアの propagate()
メソッドの役割については後述します。
続いて、TodoList
クラスのソースコードを次のように変更します。
class TodoList extends Cape.Component {
init() {
this.ds = new TaskStore();
this.ds.attach(this);
this.ds.refresh();
}
render(m) {
m.ul(m => {
this.ds.tasks.forEach(task => {
m.li(m => {
m.class({ completed: task.done });
m.label(m => {
m.onclick(e => this.ds.toggleTask(task));
m.input({ type: 'checkbox', checked: task.done }).sp();
m.span(task.title);
});
});
});
});
}
}
toggleTask()
メソッドは全体を削除します。
init()
メソッドの中身はまったく別のものとなりました。
this.ds = new TaskStore();
this.ds.attach(this);
this.ds.refresh();
1行目で TaskStore
クラスのインスタンスを作り、自身の ds
プロパティにセットしています。続いて、2行目で TaskStore
オブジェクトの attach()
メソッドに対して自分自身を引数として渡しています。
この2行目が今回のお話しの核心です。
コンポーネントが自分自身をデータストアに結び付ける(attach)ことにより、データストアの持つデータが変化したときに通知を受け取れるようになります。
データストアが結び付けられたコンポーネントにデータの変化を通知することを、伝播(propagation)と呼びます。
さきほど TaskStore
を説明したとき propagate()
メソッドの説明を後回しにしました。このメソッドはまさにこの伝播を行います。
伝播の結果として、コンポーネントの refresh()
メソッドが呼ばれます。つまり、データが変化するとコンポーネントが再描画されるのです。
データストアには複数のコンポーネントを結び付けることができます。その場合、データストアはそれらのコンポーネントに対して順にデータの変化を通知していきます。別の見方をすれば、コンポーネント群がデータストアを「observe」しているとも言えます。この見方によれば、データストアが持つデータが変化したらコンポーネントが自分自身を再描画する、ということになります。
init()
メソッドの3行目では、次のように書いています。
this.ds.refresh();
コンポーネントの refresh()
メソッドではなく、データストアの refresh()
メソッドを呼んでいる点に留意してください。つまり、TaskStore
オブジェクトにおいて次のコードが実行されることになります。
$.ajax({
type: 'GET',
url: '/api/tasks'
}).done(data => {
this.tasks = data;
this.propagate();
});
Ajax コールが行われ、データストアのデータ(this.tasks
)が更新され、「伝播」が始まります。その結果として、コンポーネントの refresh()
メソッドが呼ばれ、1回目の描画が行われます。
少しややこしいかもしれません。ソースコードを丹念に追って、処理の流れを理解してください。
TodoList
クラスの render()
メソッドにも変更があります。次の抜粋をよく見てください。
m.ul(m => {
this.ds.tasks.forEach(task => {
m.li(m => {
m.class({ completed: task.done });
m.label(m => {
m.onclick(e => this.ds.toggleTask(task));
m.input({ type: 'checkbox', checked: task.done }).sp();
m.span(task.title);
});
});
});
});
まず、2行目で this.tasks.forEach
が this.ds.tasks.forEach
に変わっています。コンポーネント自身がデータを持つのをやめて、その役割をデータストアに委譲したので、このように変更しました。
また、6行目で this.toggleTask(task)
を this.ds.toggleTask(task)
に書き換えました。toggleTask()
メソッドをデータストアに移した結果です。データストアに移された toggleTask()
メソッドでは最後に this.propagate()
が呼ばれ、データの変化がコンポーネントに「伝播」してきます。
今回の変更によってアプリケーションの振る舞いは変化していません。前回同様に Rails アプリケーションを起動し、各タスクのチェックボックスをクリックして、タスクの done
カラムの値がデータベース上で変化することを確かめてください。
次回は、タスクを新規追加するフォームを表示する方法について解説します。