Ajax によるデータの更新

2015/06/14

前回は、イベントハンドラーについて解説しました。ユーザーが HTML 要素(例えば、チェックボックス)に何らかのアクション(例えば、クリック)をしたときに実行される関数がイベントハンドラーです。

今回は、イベントハンドラーの中で Ajax コールを行いデータベースを更新する方法について説明します。


本題に入る前に、イベントハンドラーの働きを視覚的に分かりやすくするため、スタイルシートを適用します。

テキストエディタで app/assets/stylesheets ディレクトリに、新規ファイル todo_list.scss を次のような内容で作成してください。

#todo-list {
  label.completed span {
    color: #888;
    text-decoration: line-through;
  }
}

ファイル名の拡張子 .scss はこのファイルが Sass/SCSS 形式で書かれていることを意味します。SCSS は CSS を拡張したスタイルシート言語で、Rails によって自動的に CSS に変換されます。SCSS の第一の特長は、セレクタを入れ子にできることです。上の例では、todo-list という ID を持つ要素の内側にある completed クラスの label 要素のさらに内側にある span 要素のスタイルを設定しています。

そして、TodoList コンポーネントの render() メソッドを修正します。テキストエディタで app/assets/javascripts/todo_list.es6 の該当部分を次のように書き換えてください(m.label の前に1行挿入)。

  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);
          });
        });
      });
    });
  }

挿入されたのは次の1行です。

m.class({ completed: task.done });

この結果、done 属性が「真」であるタスクの場合、label 要素に completed クラスが設定されます。Rails アプリケーションを起動し、ブラウザで http://localhost:3000/ を開くと次のような画面になります。

画面キャプチャ

ここで1番目のチェックボックスをクリックすると「1」という警告ダイアローグが表示されますので、「OK」ボタンをクリックしてください。「猫のえさを買う。」という文字列のスタイルが変わらない(色がグレーにならず、取り消し線も表示されない)点を確認してください。


次に、TodoList コンポーネントの toggleTask() メソッドを修正します。テキストエディタで app/assets/javascripts/todo_list.es6 の該当部分を次のように書き換えてください。

  toggleTask(task) {
    task.done = !task.done;
    this.refresh();
  }

今回の最終的な目標は Ajax でデータベースを更新することですが、この段階ではまだそこまで作り込んでいません。まずは、TodoList コンポーネントが所有しているデータを更新し、コンポーネントのレンダリングをやり直しているだけです。

ブラウザをリロードしてから、1番目のチェックボックスをクリックしてください。先ほどと異なり、チェックを入れたり外したりするのに応じて、「猫のえさを買う。」という文字列のスタイルが変化する点に注目してください。toggleTask メソッドの引数 task は、このコンポーネントが所有しているタスクリストに含まれる特定のタスクを参照しています。その done 属性の値を反転させてから this.refresh() を呼べば、コンポーネント全体が再描画されてタスクの題名のスタイルが変化するのです。

jQuery のやり方に慣れているが「仮想 DOM」に慣れていない読者の方は、この実装方法に戸惑うかもしれません。jQuery でこの種の処理を行う場合、$(e.target).parent().toggleClass("completed"); のようなコードで直接 HTML 要素のスタイルを変更します。しかし、Cape.JS ではコンポーネント全体が再描画されるのです。こう書くと、パフォーマンスが心配になるかもしれませんが、その心配を克服するための仕組みが「仮想 DOM」です。Cape.JS のベースになっている virtual-dom ライブラリは、仮想 DOM と本物の DOM の間の差分を計算して、変更が必要な部分だけを描き直します。


続いて、Rails 側の API を実装します。まず、config/routes.rb を次のように書き換えてください。

Rails.application.routes.draw do
  root 'top#index'

  namespace :api do
    resources :tasks, only: [ :index, :update ]
  end
end

下から3行目が変更箇所です(コンマと :update を挿入)。

そして、app/controllers/api/tasks_controller.rb を次のように書き換えます。

class Api::TasksController < ApplicationController
  def index
    @tasks = Task.order(id: :asc)
  end

  def update
    task = Task.find(params[:id])
    if task.update_attributes(task_params)
      render text: 'OK'
    else
      render text: 'NG'
    end
  end

  private
  def task_params
    params.require(:task).permit(:title, :done)
  end
end

この変更の意味について説明を始めてしまうと非常に長くなってしまいます。Rails 開発未経験の方は、いったんこのままソースコードを受け入れてください。

paramsfindupdate_attributesrender などのメソッドに関しては初級の教科書やチュートリアルで学べます。task_params メソッドの役割については「Strong Parameters」というキーワードでネット検索して調べてください。


最後に、この API を呼び出すコードを TodoList コンポーネントの toggleTask() メソッドに組み入れます。

  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();
      }
    });
  }

Rails で「データの更新」を意味する PATCH メソッドで API にアクセスしています。仮に task.id の値が 1 で task.done が「偽」の場合、/api/tasks/1 という URL パスに対して { task: done: true } というデータが送信されることになります。

`/api/tasks/${task.id}` という書き方に注意してください。ECMAScript6 ではバッククオートで囲まれた文字列では、式の埋め込み(interpolation)が行われます。すなわち、${task.id} の部分には task.id の値(例えば「1」という整数)が文字列に変換されて挿入されます。

Rails アプリケーションはこれを受けてデータベースを更新し、「OK」という文字列を返します。Cape.JS 側ではそれを受けて done メソッドのパラメータ data にセットしますので、if 文の条件式が成立して当該タスクの done 属性が反転されます。そして、this.refresh() が呼ばれてコンポーネント全体が再描画される、という流れです。

では、動作確認をしましょう。ブラウザをリロードしてから、1番目のチェックボックスにチェックを入れます。すると、次のような画面に変わります。

画面キャプチャ

ここでもう一度ブラウザをリロードしてみてください。1番目のチェックボックスにチェックが入ったままになっていれば、データベースが更新されたことになります。


今回はここまでとします。説明を省略したところもあり、少し難しく感じられたかもしれません。ごめんなさい。

次回は、データストアについて解説する予定です。