【Rails】ユーザー登録フォームの作成その2　ユーザー登録の失敗【Rails Tutorial 7章まとめ】

##ユーザー登録に失敗した場合の処理
ユーザー登録に失敗した場合は、renderメソッドを使って新規登録ページに戻り、エラーメッセージを表示する。
renderを使うことで、newアクションの@user = User.newは実行されず、以前送信した内容がフォーム内に保持される。

エラーメッセージはパーシャルを使って分けておくことにする。
フォーム内にはエラーメッセージ用パーシャルへのrenderを書く。

```app/views/users/new.html
<%= form_for(@user) do |f| %>
  <%= render 'shared/error_messages' %>

  <%= f.label :name %>
  <%= f.text_field :name, class: 'form-control' %>

  <%= f.label :email %>
  <%= f.email_field :email, class: 'form-control' %>

  <%= f.label :password %>
  <%= f.password_field :password, class: 'form-control' %>

  <%= f.label :password_confirmation, "Confirmation" %>
  <%= f.password_field :password_confirmation, class: 'form-control' %>

  <%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
```
各フォームについているform_controlクラスは、bootstrap用である。

エラーメッセージ用のパーシャルは次のようである。
app/views/shared/_error_messages.html.erb

```app/views/shared/_error_messages.html
<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>
```

コントローラにまたがって共通で使うパーシャルはsharedディレクトリに入れておく。

ここで、any?メソッドはempty?メソッドの逆であり、要素が一つでもある場合はtrue、ない場合はfalseを返す。
@user.errors.countは、エラーの個数を返す。

pluralizeメソッドは、第一引数に整数を、第二引数に英単語をとり、整数値に応じて英単語を複数化する。
不規則活用にも使える。

```
>> helper.pluralize(1, "error")
=> "1 error"
>> helper.pluralize(5, "error")
=> "5 errors"
```

エラーが起こり、newページに戻されると、エラーメッセージが表示される。
また、Railsはfield_with_errorsクラスを持つdivタグによって、フォームのラベルとフォームそのものを囲んでくれる。
<img width="487" alt="スクリーンショット 2019-11-28 4.22.05.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529443/aa9db0f9-2f1e-86f1-f80a-02349ed609a7.jpeg">
これによってcssを使ってエラーを起こしたフォームの枠を赤くしたりできる。
<img width="581" alt="スクリーンショット 2019-11-28 4.23.41.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/529443/faae5157-4186-de3b-bb35-2c7943c4efb0.jpeg">
cssは以下のよう。

```app/assets/stylesheets/custom.scss
/* forms */
.
.
.
#error_explanation {
  color: red;
  ul {
    color: red;
    margin: 0 0 30px 0;
  }
}

.field_with_errors {
  @extend .has-error;
  .form-control {
    color: $state-danger-text;
  }
}
```
has_errorはbootstrapのcssクラスである。

##登録エラー時のテスト
新規ユーザー登録用の統合テストを生成する。

```
$ rails generate integration_test users_signup
```
登録エラー時のテストを書く。

```test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest

  test "invalid signup information" do
    get signup_path
    assert_template 'users/new'
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
  end
end
```
①まずsignup_pathにGETリクエストを送信する。行き先はnewアクションのnewビューである。
②newビューが表示されていることを確認する。
③assert_no_differenceを使って、ユーザー登録操作の前後でユーザー数が変わらないことを確認する。ユーザー登録が失敗すればユーザー数は変わらないはずである。
④users_pathにPOSTリクエストを送信する。postメソッドの第二引数にはparamsハッシュを指定する。
（paramsは変数でありハッシュでもあるらしい...よく分からない）
⑤ユーザー登録が失敗し、renderメソッドによってnewページに戻っていることを確認する。

必要であれば
⑥エラーメッセージが表示されていることを確認する。

```.rb
assert_select 'div#<CSS id for error explanation>'
assert_select 'div.<CSS class for field with error>'
```
###URLの修正
登録エラーでnewページに戻ると、URLが/signupから/usersに変わる。
これはusersリソースのRESTfulなルーティングによるもの。
フォームの送信は/usersのPOSTリクエストに送られて、createアクションに到達する。
その後登録エラーでnewページに戻るが、リダイレクトではなく再読み込みしていないので、URLはPOSTリクエストがされた時のままになる。
（という認識であってるかな？）

これを修正するためにルーティングを変更する。
URLが/signupのPOSTリクエストを作る（行き先はcreateアクション）。

```config/routes.rb
Rails.application.routes.draw do
  .
  .
  .
  get  '/signup',  to: 'users#new'
  post '/signup',  to: 'users#create'
  resources :users
end
```
フォームの送信先もこのルーティングに変更する。

```app/views/users/new.html.erb
<%= form_for(@user, url: signup_path) do |f| %>
  .
  .
  .
<% end %>
```
テストのPOSTリクエスト部分を修正する。また、form_forから生成されるformタグのaction属性(送信先のURLを値にとる)が正しいURLになっていることを確認する。

```test/integration/users_signup_test.rb
  test "invalid signup information" do
    get signup_path
    assert_template 'users/new'
    assert_no_difference 'User.count' do
      post signup_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'form[action="/signup"]'
  end
```