AngularJSとRuby on Railsで作るCRUDアプリ – (3)Create

2014.04.12

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

はじめに

前回構築したAngularJS+Ruby on Railsのアプリに
今回はCRUDの内のCreate機能を実装し、ウィスキーの情報を登録する画面を作成しました。

画面遷移としては
・一覧画面(index.html.erb)よりリンクをクリックし、登録画面(new.html.erb)に遷移する
・登録画面でボタンを押下すると、AngularJSにより非同期でサーバを呼び出し、登録する
・登録後、AngularJSにより一覧画面に遷移する
というようにしました。
flow3

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

実装について

1.ルーティング

routes.rbに以下のように記述し、whiskyモデルに対するCRUDのルーティングを定義します。

routes.rb
  root "whiskies#index"
  get "whiskies/list"
  resources :whiskies

2.登録画面

登録画面であるnew.html.erbです。

new.html.erb
<h1>Whiskies#new</h1>
<div ng-controller="WhiskiesNewCtrl">
    <div>Name</div><div><input type="text" ng-model="name"></div>
    <div>Price</div><div><input type="text" ng-model="price"></div>
    <button ng-click='Create();' class="btn-primary">Create</button>
</div>

「ng-controller="WhiskiesNewCtrl"」でAngularJSのコントローラを指定し
「button ng-click='Create();'」でボタン押下時に実行する
AngularJSのメソッドを指定しています。

また「ng-model=・・・」を使い、textboxの入力値をAngularJSに紐付けています。
後述しますが「ng-model=・・・」を使うと、AngularJSのControllerで、指定した名称で入力値を参照できるようになります。

3.一覧画面

トップページである一覧画面に、登録画面へのリンクを追加します。

index.html.erb
<h1>Whiskies#index</h1>
<%= link_to "new whisky", new_whisky_path, :class => 'btn btn-mini' %>
<div ng-controller="WhiskiesCtrl">
    <ul>
      <li ng-repeat="whisky in data">
        {{whisky.name}}
        <p>${{whisky.price}}</p>
      </li>
    </ul>
</div>

「link_to・・・」というのが、登録画面へのリンクです。
ただし、このままでは登録画面に遷移はしますが、次画面でAngularJSのコードが読み込まれません。

4.TurboLinkの無効化

Rails4からはTurboLinkがデフォルトで有効となっていますが
TurboLinkが有効である場合、遷移先の画面でAngularJSが読み込まれない事象が発生します。

このため、TurboLinkを無効にするのが、手っ取り早い対応法かと思われます。
対応法は
・application.jsの「//= require turbolinks」を消去
・application.html.erbの「stylesheet_link_tag」「javascript_include_tag」より
 「"data-turbolinks-track" => true 」を消去
することです。

参考
Bootstrapping an AngularJS app in Rails 4.0 - Part 1

5.RailsのController

whiskies_controller.rb
  def new
  end

  def create
    whisky = Whisky.new(:name => params[:name], :price => params[:price])
    whisky.save
    render :nothing => true
  end

登録画面(new.html.erb)を表示するためのnewアクションと、データを登録するためのcreateアクションです。

createアクションは、後ほど書くAngularJSより呼び出され、データを登録します。
非同期通信で呼び出されるので画面を返却しないよう、「render :nothing・・・」としてあります。

5.AngularJSのController

いよいよ、AngularJSについてです。
上述した通り、登録画面のボタンを押下すると呼び出され、Railsのcreateアクションを呼び出します。

controllers.js
'use strict';

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

whiskiesListApp.config(
    ["$httpProvider", 
        function($httpProvider) {
            $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
        }
    ]
);

(中略)

whiskiesListApp.controller('WhiskiesNewCtrl', function ($scope, $http, $window) {
    $scope.Create = function() {
        $http.post('/whiskies', {'name': $scope.name, 'price': $scope.price}
        ).success(function(data, status, headers, config) {
            $window.location.href = '/'
        }).error(function(data, status) {
            console.log('error:' + status);
        });
    }
});

先ずは「whiskiesListApp.controller('WhiskiesNewCtrl・・・」の所を見ていきます。

「$http.post('/whiskies', {'name': $scope.name, 'price': $scope.price}」は
・「.post('/whiskies'」で、Post先のURLを指定
・「{'name': $scope.name, 'price': $scope.price}」で、入力値をハッシュ形式でPostの引数として渡す
ことを行っています。

次にPostが正常に終了した場合、「$window.location.href = '/'」でルートパスに遷移するようにしています。

ここでルートパス、'/whiskies'について確認するため $ rake routes を行うと、以下のようになります。

         root GET    /                            whiskies#index
whiskies_list GET    /whiskies/list(.:format)     whiskies#list
     whiskies GET    /whiskies(.:format)          whiskies#index
              POST   /whiskies(.:format)          whiskies#create

・ルートパス「/」は前回作成したindexアクションが
・「whiskies」でPostするとcreateアクションが
実行されることが分かるかと思います。

次に、「whiskiesListApp.config(・・・」についてです。
Railsでは、AngularJS(を含むAjax通信)でPostする場合、認証用のトークンを送らないと「422」エラーとなることがあります。

このため、認証用トークンを渡すため「whiskiesListApp.config(・・・」にて、トークンを設定しています。

最後に

以上です。
・TurboLinkを無効化する
・認証用のトークンを渡す
と、ちょっとRailsの癖を考慮する必要がありました。