Signup error messages

現在要加入有用的錯誤訊息,說明註冊失敗的原因。Rails 基於在 User model 驗證上,提供了這樣的訊息,例如試著提供無效的 email 和密碼,然後儲存:

$ rails console
>> user = User.new(name: "Foo Bar", email: "foo@invalid",
?>                 password: "dude", password_confirmation: "dude
>> user.save
=> false
>> user.errors.full_messages
=> ["Email is invalid", "Password is too short (minimum is 6 characters)"]

errors.full_messages 物件是一個包含錯誤訊息的陣列。在上面的 console 中,當嘗試儲存錯誤資料的時候,就會產生跟 @user 物件相關的錯誤訊息。

為了在頁面上顯示錯誤訊息,可以在 new.html.erb 頁面上 render 一個 error-messages partial,同時也替每一個輸入欄位加上 CSS form-control

app/views/users/new.html.erb

<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= 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 %>
  </div>
</div>

<%= render 'shared/error_messages' %> 這段就是 render shared/error_messages 的 partical 檔案,按照 Rails 慣例,shared/ 目錄是放置可以讓好幾個 controller 的 view 共用的 partial 檔案。

所以這表示我們要建立一個 app/views/shared 目錄:

$ mkdir app/views/shared

接著建立 _error_messages.html.erb partial 檔案:

app/views/shared/_error_messages.html.erb

<% 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 %>

這個 partial 檔案包含了幾個新的 Rails 和 Ruby 架構,包括 Rails error 物件的兩個方法。第一個方法是 count,回傳錯誤的數量:

> user.errors.count
=> 2

另一個方法是 any?,作用跟 empty? 相反:

> user.errors.empty?
=> false
> user.errors.any?
=> true

如果物件為空,empty? 會回傳 true,反之回傳 falseany? 則是在物件存在時,回傳 true,反之回傳 false。(countempty?any? 也可以用在陣列上)

另外還有 pluralize 方法,預設在 console 無法使用,但可以透過 include ActionView::Helpers::TextHelper module 來使用它:

> include ActionView::Helpers::TextHelper
> pluralize(1, "error")
=> "1 error"
> pluralize(5, "error")
=> "5 errors"

pluralize 的第一個參數是整數(integer),然後會回傳這個整數的正確單複數形,給第二個參數使用。pluralize 方法是由功能強大的 inflector 實作,可以處理大多數單字的單複數轉換,甚至包括不規則的複數形:

> pluralize(2, "woman")
=> "2 women"
> pluralize(3, "erratum")
=> "3 errata"

所以這段程式碼:

<%= pluralize(@user.errors.count, "error") %>

可以回傳 "0 errors""1 error""2 errors" 之類的字串,可以避免出現像 1 errors 這種錯誤。

不過其實這是針對英文,中文就沒差了,1 個「錯誤」跟 5 個「錯誤」,都一樣XDDD

接著來增加樣式。Rails 會自動把錯誤訊息包在含有 field_with_errors class 的 div 區塊裡。然後可以使用 Sass 的@extend function 把 Bootstrap 的 has-error class 引進樣式裡:

app/assets/stylesheets/custom.css.sass

.
.
.
/* 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

最後畫面會長這樣:

因為錯誤訊息是由 model 驗證產生的,所以之後如果修改驗證規則,錯誤訊息就會跟著改變。由於我們加了存在性驗證(presence validation),加上 has_secure_password 方法換驗證是否有沒有密碼(密碼是否為 nil),所以如果使用者沒有輸入密碼,目前會出現重複的錯誤訊息,之後會增加 allow_nil: true 方法,來解決這個問題。