Step by Step Ruby on Rails

Ruby on Railsで実際にWebサイトを構築する手順をまとめています。

Ruby on Rails Tutorial Railsでバリデーションチェック、AngularJSのビューにエラーメッセージ表示

「Ruby on Rails Tutorial」のサンプルアプリをAngularJSとBootstrap3を使う形にして作成します。ユーザー登録時のバリデーションチェックについては、AngularJS側でのチェックは前回設定しました。今回は、サーバー側でRailsの機能でバリデーションチェックを行い、エラーコメントをAngularJSビューで表示します。

(1)Railsモデルにバリデーションを登録

nameとemailカラムに対し、下記チェックを行います。

・password
文字列長6
・name
存在チェック、文字列長20
・email
存在チェック、フォーマットチェック、一意性チェック、

$ vi app/models/user.rb

class User < ActiveRecord::Base
  validates :password, length: { minimum: 6 }
  validates :name,  presence: true, length: { maximum: 20 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

(2)バリデーションエラーメッセージをAngularJSコントローラで設定

Rails側でのバリデーションエラーメッセージは、下記のようにJSONフォーマットでリターンしています。

render json: @user.errors, status: :unprocessable_entity

1)underscore.jsをインクルード

以下でunderscoreの機能を使用するので下記設定を追加します。

・underscore-min.jsを入手し、vendor/assets/javascriptsディレクトリに配置。

・application.jsに追加

$ vi app/assets/javascripts/application.js

//= require underscore-min

下記②でundersocoreのeachメソッドを使ってサーバーからリターンされたエラーメッセージを取り出します。

※underscoreのeachメソッドの使用方法

①配列
var array = [10, 20, 30];
_.each(array, function(element, index) {
  console.log(element + ' : ' + index);
});

// 結果
10 : 0
20 : 1
30 : 2

②オブジェクト
var object = {
  name: "test",
  email: "test@example.com
};
_.each(object, function(value, key) {
  console.log(key + ' : ' + value);
});

// 結果
name : "test"
email : "test@example.com"

2)AngularJSの"UsersNewCtrl"コントローラにバリデーションエラーメッセージの設定追加

$resourceサービスのcreateアクション失敗時のコールバック関数にバリデーションメッセージを設定します。

$ vi app/assets/javascripts/mymodule.js.erb

myModule.controller("UsersNewCtrl", function($scope, userResource, $location, flashService) {
  :
  $scope.submit = function() {
  :
    function failure(response) {
      _.each(response.data, function(errors, key) {
        _.each(errors, function(e) {
          $scope.userNewForm[key].$dirty = true;
          $scope.userNewForm[key].$setValidity(e, false);
        });
      });
    }
    userResource.create($scope.user, success, failure);
  };
  $scope.errorMessage = function(name) {
    var s = $scope.userNewForm[name].$error;
    result = [];
    _.each(s, function(key, value) {
      result.push(name + " " + value);
    });
    return result.join(", ");
  };
});

●上記コードの説明

・入力データ
data:Object
  email:Array
    0: "has already taken"
  password:Array
    0: "is too short (minimum is 6 characters)"

①failure(response)関数

・フォーム内のemailとpasswordの$dirty属性をtrueにする。

$scope.userNewForm[key].$dirty = true;

・$setValidityメソッドを使って、email、passwordのフォームコントロールのステータスをinvalidにし、$error属性にエラーメッセージをセットする。

$scope.userNewForm[key].$setValidity(e, false);

②errorMessage関数

上記①で$error属性にセットしたエラーメッセージを取り出し、ビューにリターンする。

"email has already taken"、"password is too short (minimum is 6 characters)"のメッセージがリターンされる。

3)フォームのエラー状態をリセット

サーバーからのエラーメッセージによって設定したフォームのエラー状態は、その後クライアント側でフォーム内に正しい値を入力しても解除されないので、エラーリセットボタンを設け、フォームのエラー状態を解除する。

  $scope.resetError = function(form) {
    if (form) {
      form.$setPristine();
      form.$setUntouched();
      form.$setValidity();
    }
  }

(3)AngularJSビューにエラーを表示する記述追加

$ vi app/assets/templates/users/new.html.erb

<div ng-controller="UsersNewCtrl" class="row">
<style>
  form.ng-invalid.ng-dirty { background-color: lightpink; }
  form.ng-valid.ng-dirty { background-color: lightgreen; }
  form { padding: 10px;}
</style>
  <div class="col-md-6 col-md-offset-3">
    <h1 class="text-center">Sign up</h1>
    <form name="userNewForm" novalidate>
      <div class="well">
        <div class="form-group" ng-class="{'has-error': userNewForm.name.$invalid && userNewForm.name.$dirty}">
          <label>Name</label>
          <input name="name" class="form-control"
            ng-model="user.name"
            required
            ng-maxlength="20" />
          <span class="text-danger" ng-show="userNewForm.name.$dirty && userNewForm.name.$error.required">
            Name can't be blank
          </span>
          <span class="text-danger" ng-show="userNewForm.name.$dirty && userNewForm.name.$error.maxlength">
            Name is too long (maximum is 20 characters)
          </span>
          <span class="text-danger" ng-show="userNewForm.name.$dirty && userNewForm.name.$invalid">
            {\{errorMessage('name')}}
          </span>
        </div>
        <div class="form-group" ng-class="{'has-error': userNewForm.email.$invalid && userNewForm.email.$dirty}">
          <label>Email</label>
          <input type="email" name="email" class="form-control"
            ng-model="user.email"
            required />
          <span class="text-danger" ng-show="userNewForm.email.$dirty && userNewForm.email.$error.required">
            Email can't be blank
          </span>
          <span class="text-danger" ng-show="userNewForm.email.$dirty && userNewForm.email.$error.email">
            Email is invalid
          </span>
          <span class="text-danger" ng-show="userNewForm.email.$dirty && userNewForm.email.$invalid">
            {\{errorMessage('email')}}
          </span>
        </div>
        <div class="form-group" ng-class="{'has-error': userNewForm.password.$invalid && userNewForm.password.$dirty}">
          <label>Password</label>
          <input type="password" name="password" class="form-control"
            ng-model="user.password"
            required
            ng-minlength="6" />
          <span class="text-danger" ng-show="userNewForm.password.$dirty && userNewForm.password.$error.required">
            Password can't be blank
          </span>
          <span class="text-danger" ng-show="userNewForm.password.$dirty && userNewForm.password.$error.maxlength">
            Password is too short (minimum is 6 characters)
          </span>
          <span class="text-danger" ng-show="userNewForm.password.$dirty && userNewForm.password.$invalid">
            {\{errorMessage('password')}}
          </span>
        </div>
        <div class="form-group" ng-class="{'has-error': userNewForm.password_confirmation.$invalid && userNewForm.password_confirmation.$dirty}">
          <label>Password_confirmation</label>
            <input type="password" name="password_confirmation"
              class="form-control"
              ng-model="user.password_confirmation"
              required
              ng-minlength="6" />
          <span class="text-danger" ng-show="userNewForm.password_confirmation.$dirty && userNewForm.password_confirmation.$error.required">
            Password_confirmation can't be blank
          </span>
          <span class="text-danger" ng-show="userNewForm.password_confirmation.$dirty && userNewForm.password_confirmation.$error.maxlength">
            Password_confirmation is too short (minimum is 6 characters)
          </span>
          <span class="text-danger" ng-show="userNewForm.password_confirmation.$dirty && userNewForm.password_confirmation.$invalid">
            {\{errorMessage('password_confirmation')}}
          </span>
        </div>
        <button ng-click="submit()" class="btn btn-primary"
                 ng-disabled="userNewForm.$invalid" >
          Create my account
        </button>
        <button ng-click="resetError(userNewForm)" class="btn btn-primary">
          Reset Error
        </button>
      </div>
    </form>
  </div>
</div>