AngularJSとRuby on Railsで作るCRUDアプリ – (2)Read

2014.04.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回構築したAngularJS+Ruby on Railsのアプリに、今回はCRUDの内のRead機能を実装し
DBに登録してあるウィスキーの一覧を表示する画面を作成しました。

index

以下に、実装する上でのポイントとなる箇所を記述していきます。
尚、ソースコードは以下のGitHubに置いてあるので、全ソースを見たい方は参考にしてください。

AngularjsWhiskyList

実装について

1.application.js

application.jsに、AngularJSのスクリプトを読み込むよう、以下の定義を追加します。
(ベタに参照するjsを書いているので、本当はフォルダを指定した方がイイと思いますが・・・)

//= require ngapp/app/lib/angular/angular.min.js
//= require ngapp/app/lib/angular/angular-resource.min.js
//= require ngapp/app/js/controllers.js

2.Modelの作成とデータの登録

一覧に表示するWhiskyモデルとテーブルを作成します。

$ rails g model Whisky name:string price:decimal
$ rake db:migrate
$ rake db:migrate RAILS_ENV=test

3.サーバ側のController

Whiskyテーブルのデータを取得し、json形式で返却するactionをRailsのControllerに追加します。

whiskies_controller.rb
class WhiskiesController < ApplicationController
  def index
  end

  def list
    data = Whisky.all
    render :json => data 
  end
end

ここでは、データをjson形式で返す専用のaction(list)を
Viewを表示するaction(index)とは別に用意しました。

クライアント側の処理は以下に記述するAngularJSにて行うため、
サーバ側での処理はデータのCRUD(ここではRead)のみになります。

4.クライアント側のControllerとテスト

クライアント側では、AngularJS内より
上記で書いたRailsの「list」actionを呼び出し、データを取得します。

vendor/assets/javascripts/ngapp/app/js/controllers.js
'use strict';

/* Controllers */
var whiskiesListApp = angular.module('whiskiesListApp', []);

whiskiesListApp.controller('WhiskiesCtrl', function ($scope, $http) {

    $http.get('whiskies/list').success(function(data) {
        $scope.data = data;
    });

});

angular.module('whiskiesListApp', ) で、AngularJSのモジュール名を指定しています。
このモジュール名とは、今のところ名前空間のようなものだと認識しておけばいいかと思います。

whiskiesListApp.controller('WhiskiesCtrl'・・・で、モジュールに「WhiskiesCtrl」Controllerを
追加しています。

Controllerの中では、$httpオブジェクトのgetメソッドを使用し、Railsのactionを呼び出し
取得したデータを$scopeオブジェクトのdata変数に格納しています。

ここでの$scopeオブジェクトとは、後述するViewとControllerとでデータを共有するための
オブジェクトだと、今のところは考えてください。

このように、AngularJSのControllerの処理の流れは
1.データの取得
2.Viewで参照する変数への格納
と、RailsのControllerと似た流れとなります。

では、このControllerに対するテストです。

/vendor/assets/javascripts/ngapp/test/unit/controllersSpec.js
'use strict';

/* jasmine specs for controllers go here */
describe('AngularjsWhiskyList controllers', function() {

    beforeEach(module('whiskiesListApp'));

    describe('WhiskiesCtrl', function(){
        var scope, httpBackend;

        beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http) {
            scope = $rootScope.$new();
            httpBackend = $httpBackend;
            httpBackend.when("GET", "whiskies/list").respond([
                {name:'Laphroaig Quarter Cask', price:49.99},
                {name:'Johnnie Walker Black', price:33.97},
                {name:'Canadian Club', price:19.99}
            ]);
            $controller('WhiskiesCtrl', {
                $scope: scope,
                $http: $http
            });
        }));

        it('should get "data" model', function () {
            httpBackend.flush();
            expect(scope.data.length).toBe(3);
            expect(scope.data[0].name).toBe('Laphroaig Quarter Cask');
            expect(scope.data[0].price).toBe(49.99);
        });
    });

});

JavaScriptのテストモジュールである「Jasmine」を使い、テストを記述しています。
書式としてはRSpecに似ていることが分かるかと思います。

このテストのポイントは、$httpBackendオブジェクトを使い
サーバより受け取るデータのモックを作成しているところです。

これにより、サーバ側(Railsの処理)とは独立して、クライアント側(AngularJS)の
処理を実装、テストすることができます。

Jasmineのテストの実行は、コンソールにて/vendor/assets/javascripts/ngapp
へ移動し、test.shを起動することで行います。

$ ./scripts/test.sh

5.Viewの実装

最後にViewです。先ずはViewのテンプレートであるapplication.html.erbに
今回使うAngularJSのモジュール名を記述します。

application.html.erb
<html lang="en" ng-app="whiskiesListApp">

次に一覧画面であるindex.html.erbです。

index.html.erb
<h1>Whiskies#index</h1>
<div ng-controller="WhiskiesCtrl">
    <ul>
      <li ng-repeat="whisky in data">
        {{whisky.name}}
        <p>${{whisky.price}}</p>
      </li>
    </ul>
</div>

ng-controller="WhiskiesCtrl" で呼び出すControllerを指定しています。
ng-repeat="whisky in data" で、Controller内で設定したdataオブジェクト
を参照し、ループして一件ずつ表示しています。

このように、RailsのViewと同じ様に、AngularJSのViewの記述も
Controllerで設定した変数を参照して行います。

最後に

以上です。
AngularJSのControllerとViewは、Railsのそれと似た処理に流れになることが
なんとなく伝わればと思います。