実践入門 Ember.js

第7回 データの永続化(Ember Data)

この記事を読むのに必要な時間:およそ 11 分

外部のAPIを扱う

せっかくなので,既存のAPIサーバに対してEmber Dataを使ってクライアントアプリを作る例も紹介します。

サンプルとしてGitHubのユーザ名を検索すると,そのユーザが最近starをつけたリポジトリを見られる」アプリを作ってみましょう。完成イメージは次のとおりです。

  1. GitHubでのログイン名を入力してエンターキーを押します。

    画像

  2. ユーザの最近のお気に入り一覧が表示されます。

    画像

今回対象とするAPIはGitHub APIです。

まずはこのAPIを使うようアダプタを設定します。

App.ApplicationAdapter = DS.RESTAdapter.extend({
  host: 'https://api.github.com'
});

今回はユーザとリポジトリさえあれば十分なので,以下のドキュメントを参考にモデルを定義します。

App.User = DS.Model.extend({
  login: DS.attr('string'),
  avatar_url: DS.attr('string'),

  starred: DS.hasMany('repo', {async: true})
});

App.Repo = DS.Model.extend({
  name: DS.attr('string'),
  full_name: DS.attr('string'),
  html_url: DS.attr('string'),
  description: DS.attr('string'),
  language: DS.attr('string'),
  watchers: DS.attr('number'),
  owner: DS.attr() // owner はオブジェクトが渡ってくるので型変換は行わない
});

次は,GitHub APIのレスポンスを上手くモデルにマッピングするために,シリアライザを定義します。

App.UserSerializer = DS.RESTSerializer.extend({
  primaryKey: 'login',

  normalizePayload: function(payload) {
    payload.links = {
      starred: payload.url + '/starred'
    };

    return {
      user: payload
    };
  }
});

App.RepoSerializer = DS.RESTSerializer.extend({
  normalizePayload: function(payload) {
    return {
      repo: payload
    };
  }
});

細かく見ていきましょう。

primaryKey

プライマリーキーとして扱うプロパティを指定します。デフォルトはidです。GitHub APIではユーザのJSONにidが含まれているのですが,今回のアプリケーションではログイン名をキーにしてユーザを取得するため,モデルを簡単に扱うためにloginプロパティをIDとみなすことにします。

normalizePayload

APIから取得したJSONをEmber Dataで扱う形に変換するためのメソッドです。引数にはサーバから取得したJSONがそのまま渡ってくるので,先ほど紹介した形に加工して戻り値に指定します。デフォルトの実装では引数で渡されたJSONをそのまま返すようになっています。

ここまででGitHub APIを扱う準備が完成しました。あとはルーティングとテンプレートを作成して実際に動かしてみましょう。

ルーティングは以下のとおりです。

App.Router.map(function() {
  this.resource('user', {path: 'user/:id'});
});

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return this.store.all('user');
  }
});

App.UserRoute = Ember.Route.extend({
  model: function(params) {
    return this.store.find('user', params.id);
  }
});

App.IndexController = Ember.Controller.extend({
  name: null,

  actions: {
    addUser: function() {
      var user = this.store.find('user', this.get('name'));

      this.set('name', null);

      this.transitionToRoute('user', user);
    }
  }
});

テンプレートは次のとおりです。

<script type="text/x-handlebars">
  <h1>GitHub star viewer</h1>

  {{outlet}}
</script>

<script type="text/x-handlebars" data-template-name="index">
  @{{input value=name action="addUser" placeholder="username(GitHub)"}}

  {{#if model.length}}
    <h2>最近検索したユーザ</h2>
  {{/if}}
  <ul>
  {{#each user in model}}
    <li>
      <img {{bind-attr src=user.avatar_url}} width="20px" height="20px"/>
      {{link-to user.id "user" user}}
    </li>
  {{/each}}
  </ul>
</script>

<script type="text/x-handlebars" data-template-name="user">
  <img {{bind-attr src=model.avatar_url}} width="20px" height="20px"/>
{{model.id}}

<ul>
  最近 star したリポジトリ
  {{#each repo in model.starred}}
    <li>
      <img {{bind-attr src=repo.owner.avatar_url}} width="20px" height="20px"/>
      <a {{bind-attr href=repo.html_url}} target="_blank">{{repo.full_name}}</a>
      [{{repo.watchers}}]
      #{{repo.language}}
      <div>
        {{repo.description}}
      </div>
    </li>
  {{/each}}
</ul>

{{link-to "戻る" "index"}}
</script>

このように,モデルを定義した上でシリアライザを適切に設定すると,既存のAPIに対するクライアントを簡単に作成できます。

まとめ

今回はEmber Dataについて解説しました。Ember Dataには今回解説したもの以外にもいつくかの部品が存在します。さらに詳しい説明は公式ドキュメントを参照してください。

次回は,開発およびデプロイには欠かせないビルドツールであるEmber CLIember-railsを解説する予定です。

著者プロフィール

佐藤竜之介(さとうりゅうのすけ)

株式会社えにしテック所属。JavaScriptとRubyが好きなウェブ系プログラマ。オープンソースソフトウェアに強い関心がありEmber.jsにも数多くのパッチを送っている。

ブログ:http://tricknotes.hateblo.jp/
Twitter:@tricknotes