<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 7.3 注冊失敗 雖然上一節大概介紹了[圖 7.12](#fig-signup-form) 中表單的 HTML 結構(參見[代碼清單 7.15](#listing-signup-form-html)),但并沒涉及什么細節,其實注冊失敗時才能更好地理解這個表單的作用。本節,我們會在注冊表單中填寫一些無效的數據,提交表單后,頁面不會轉向其他頁面,而是返回“注冊”頁面,顯示一些錯誤消息,如[圖 7.14](#fig-signup-failure-mockup) 中的構思圖所示。 ![signup failure mockup bootstrap](https://box.kancloud.cn/2016-05-11_5732bd14c7b0a.png)圖 7.14:注冊失敗時顯示的頁面構思圖 ## 7.3.1 可正常使用的表單 回顧一下 [7.1.2 節](#a-users-resource)的內容,在 `routes.rb` 文件中設置 `resources :users` 之后([代碼清單 7.3](#listing-users-resource)),Rails 應用就可以響應[表 7.1](#table-restful-users)中符合 REST 架構的 URL 了。其中,發送到 /users 地址上的 `POST` 請求由 `create` 動作處理。在 `create` 動作中,我們可以調用 `User.new` 方法,使用提交的數據創建一個新用戶對象,嘗試存入數據庫,失敗后再重新渲染“注冊”頁面,讓訪客重新填寫注冊信息。我們先來看一下生成的 `form` 元素: ``` <form action="/users" class="new_user" id="new_user" method="post"> ``` [7.2.2 節](#signup-form-html)說過,這個表單會向 /users 地址發送 `POST` 請求。 為了讓這個表單可用,首先我們要添加[代碼清單 7.16](#listing-first-create-action) 中的代碼。這段代碼再次用到了 `render` 方法,上一次是在局部視圖中([5.1.3 節](chapter5.html#partials)),不過如你所見,在控制器的動作中也可以使用 `render` 方法。同時,我們在這段代碼中介紹了 `if-else` 分支結構的用法:根據 `@user.save` 的返回值,分別處理用戶存儲成功和失敗兩種情況([6.1.3 節](chapter6.html#creating-user-objects)介紹過,存儲成功時返回值為 `true`,失敗時返回值為 `false`)。 ##### 代碼清單 7.16:能處理注冊失敗的 `create` 動作 app/controllers/users_controller.rb ``` class UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end def create @user = User.new(params[:user]) # 不是最終的實現方式 if @user.save # 處理注冊成功的情況 else render 'new' end end end ``` 留意上述代碼中的注釋——這不是最終的實現方式,但現在完全夠用。最終版會在 [7.3.2 節](#strong-parameters)實現。 我們要實際操作一下,提交一些無效的注冊數據,這樣才能更好地理解[代碼清單 7.16](#listing-first-create-action) 中代碼的作用,結果如[圖 7.15](#fig-signup-failure) 所示,底部完整的調試信息如[圖 7.16](#fig-signup-failure-rails-debug) 所示。([圖 7.15](#fig-signup-failure) 中還顯示了 Web 控制臺,這是個 Rails 控制臺,只不過顯示在瀏覽器中,用來協助調試。我們可以在其中查看用戶模型,不過這里我們想審查 `params`,可是在 Web 控制臺中無法獲取。) ![signup failure 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd14dc4c7.png)圖 7.15:注冊失敗![signup failure debug 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd15054b5.png)圖 7.16:注冊失敗時顯示的調試信息 下面我們來分析一下調試信息中請求參數哈希的 `user` 部分([圖 7.16](#fig-signup-failure-rails-debug)),以便深入理解 Rails 處理表單的過程: ``` "user" => { "name" => "Foo Bar", "email" => "foo@invalid", "password" => "[FILTERED]", "password_confirmation" => "[FILTERED]" } ``` 這個哈希是 `params` 的一部分,會傳給用戶控制器。[7.1.2 節](#a-users-resource)說過,`params` 哈希中包含每次請求的信息,例如向 /users/1 發送請求時,`params[:id]` 的值是用戶的 ID,即 1。提交表單發送 `POST` 請求時,`params` 是一個嵌套哈希。嵌套哈希在 [4.3.3 節](chapter4.html#hashes-and-symbols)中使用控制臺介紹 `params` 時用過。上面的調試信息說明,提交表單后,Rails 會構建一個名為 `user` 的哈希,哈希中的鍵是 `input` 標簽的 `name` 屬性值([代碼清單 7.13](#listing-signup-form)),鍵對應的值是用戶在字段中填寫的內容。例如: ``` <input id="user_email" name="user[email]" type="email" /> ``` `name` 屬性的值是 `user[email]`,表示 `user` 哈希中的 `email` 元素。 雖然調試信息中的鍵是字符串形式,不過卻以符號形式傳給用戶控制器。`params[:user]` 這個嵌套哈希實際上就是 `User.new` 方法創建用戶所需的參數。我們在 [4.4.5 節](chapter4.html#a-user-class)介紹過 `User.new` 的用法,[代碼清單 7.16](#listing-first-create-action) 也用到了。也就是說,如下代碼: ``` @user = User.new(params[:user]) ``` 基本上等同于 ``` @user = User.new(name: "Foo Bar", email: "foo@invalid", password: "foo", password_confirmation: "bar") ``` 在舊版 Rails 中,使用 ``` @user = User.new(params[:user]) ``` 就行了,但默認情況下這種用法并不安全,需要謹慎處理,避免惡意用戶篡改應用的數據庫。在 Rails 4.0 之后的版本中,這行代碼會拋出異常(如[圖 7.15](#fig-signup-failure) 和[圖 7.16](#fig-signup-failure-rails-debug) 所示),增強了安全。 ## 7.3.2 健壯參數 我們在 [4.4.5 節](chapter4.html#a-user-class)提到過“批量賦值”——使用一個哈希初始化 Ruby 變量,如下所示: ``` @user = User.new(params[:user]) # 不是最終的實現方法 ``` 上述代碼中的注釋[代碼清單 7.16](#listing-first-create-action) 中也有,說明這不是最終的實現方式。因為初始化整個 `params` 哈希十分危險,會把用戶提交的所有數據傳給 `User.new` 方法。假設除了前述的屬性,用戶模型中還有一個 `admin` 屬性,用來標識網站的管理員。(我們會在 [9.4.1 節](chapter9.html#administrative-users)加入這個屬性。)如果想把這個屬性設為 `true`,要在 `params[:user]` 中包含 `admin='1'`。這個操作可以使用 `curl` 等命令行 HTTP 客戶端輕易實現。如果把整個 `params` 哈希傳給 `User.new`,那么網站中的任何用戶都可以在請求中包含 `admin='1'` 來獲取管理員權限。 舊版 Rails 使用模型中的 `attr_accessible` 方法解決這個問題,在一些早期的 Rails 應用中可能還會看到這種用法。但是,從 Rails 4.0 起,推薦在控制器層使用一種叫做“健壯參數”(strong parameter)的技術。這個技術可以指定需要哪些請求參數,以及允許傳入哪些請求參數。而且,如果按照上面的方式傳入整個 `params` 哈希,應用會拋出異常。所以,現在默認情況下,Rails 應用已經堵住了批量賦值漏洞。 本例,我們需要 `params` 哈希包含 `:user` 元素,而且只允許傳入 `name`、`email`、`password` 和 `password_confirmation` 屬性。我們可以使用下面的代碼實現: ``` params.require(:user).permit(:name, :email, :password, :password_confirmation) ``` 這行代碼會返回一個 `params` 哈希,只包含允許使用的屬性。而且,如果沒有指定 `:user` 元素還會拋出異常。 為了使用方便,可以定義一個名為 `user_params` 的方法,換掉 `params[:user]`,返回初始化所需的哈希: ``` @user = User.new(user_params) ``` `user_params` 方法只會在用戶控制器內部使用,不需要開放給外部用戶,所以我們可以使用 Ruby 中的 `private` 關鍵字[[9](#fn-9)]把這個方法的作用域設為“私有”,如[代碼清單 7.17](#listing-create-action-strong-parameters) 所示。(我們會在 [8.4 節](chapter8.html#remember-me)詳細介紹 `private`。) ##### 代碼清單 7.17:在 `create` 動作中使用健壯參數 app/controller/users_controller.rb ``` class UsersController < ApplicationController . . . def create @user = User.new(user_params) if @user.save # 處理注冊成功的情況 else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end ``` 順便說一下,`private` 后面的 `user_params` 方法多了一層縮進,目的是為了從視覺上容易辨認哪些是私有方法。(經驗證明,這么做很明智。如果一個類中有很多方法,容易不小心把方法定義為“私有”,在相應的對象上無法調用時會覺得非常奇怪。) 現在,注冊表單可以使用了,至少提交后不會顯示錯誤了。但是,如[圖 7.17](#fig-invalid-submission-no-feedback),提交無效數據后,(除了只在開發環境中顯示的調試信息之外)表單沒有顯示任何反饋信息,容易讓人誤解。而且也沒真正創建一個新用戶。第一個問題在 [7.3.3 節](#signup-error-messages)解決,第二個問題在 [7.4 節](#successful-signups)解決。 ![invalid submission no feedback](https://box.kancloud.cn/2016-05-11_5732bd15257db.png)圖 7.17:提交無效信息后顯示的注冊表單 ## 7.3.3 注冊失敗錯誤消息 處理注冊失敗的最后一步,要加入有用的錯誤消息,說明注冊失敗的原因。默認情況下,Rails 基于用戶模型的驗證,提供了這種消息。假設我們使用無效的電子郵件地址和長度較短的密碼創建用戶: ``` $ 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_message` 對象是一個由錯誤消息組成的數組([6.2.2 節](chapter6.html#validating-presence)簡介過)。 和上面的控制臺會話類似,在[代碼清單 7.16](#listing-first-create-action) 中,保存失敗時也會生成一組和 `@user` 對象相關的錯誤消息。如果想在瀏覽器中顯示這些錯誤消息,我們要在 `new` 視圖中渲染一個錯誤消息局部視圖,并把表單中每個輸入框的 CSS 類設為 `form-control`(在 Bootstrap 中有特殊意義),如[代碼清單 7.18](#listing-f-error-messages) 所示。注意,這個錯誤消息局部視圖只是臨時的,最終版會在 [11.3.2 節](chapter11.html#creating-microposts)實現。 ##### 代碼清單 7.18:在注冊表單中顯示錯誤消息 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> ``` 注意,在上面的代碼中,渲染的局部視圖名為 `shared/error_messages`,這里用到了 Rails 的一個約定:如果局部視圖要在多個控制器中使用([9.1.1 節](chapter9.html#edit-form)),則把它存放在專門的 `shared/` 文件夾中。所以我們要使用 `mkdir`([表 1.1](chapter1.html#table-unix-commands))新建 `app/views/shared` 文件夾: ``` $ mkdir app/views/shared ``` 然后像之前一樣,在文本編輯器中新建局部視圖 `_error_messages.html.erb` 文件。這個局部視圖的內容如[代碼清單 7.19](#listing-errors-partial) 所示。 ##### 代碼清單 7.19:顯示表單錯誤消息的局部視圖 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 %> ``` 這個局部視圖的代碼使用了幾個之前沒用過的 Rails/Ruby 結構,還有 Rails 錯誤對象上的兩個新方法。第一個新方法是 `count`,它的返回值是錯誤的數量: ``` >> user.errors.count => 2 ``` 第二個新方法是 `any?`,它和 `empty?` 的作用相反: ``` >> user.errors.empty? => false >> user.errors.any? => true ``` 第一次使用 `empty?` 方法是在 [4.2.3 節](chapter4.html#objects-and-message-passing),用在字符串上;從上面的代碼可以看出,`empty?` 也可用在 Rails 錯誤對象上,如果錯誤對象為空返回 `true`,否則返回 `false`。`any?` 方法就是取反 `empty?` 的返回值,如果對象中有內容就返回 `true`,沒內容則返回 `false`。(順便說一下,`count`、`empty?` 和 `any?` 都可以用在 Ruby 數組上,[11.2 節](chapter11.html#showing-microposts)會好好利用這三個方法。) 還有一個比較新的方法是 `pluralize`,在控制臺中默認不可用,不過我們可以引入 `ActionView::Helpers::TextHelper` 模塊,加載這個方法:[[10](#fn-10)] ``` >> include ActionView::Helpers::TextHelper >> pluralize(1, "error") => "1 error" >> pluralize(5, "error") => "5 errors" ``` 如上所示,`pluralize` 方法的第一個參數是整數,返回值是這個數字和第二個參數組合在一起后,正確的單復數形式。`pluralize` 方法由功能強大的“轉置器”(inflector)實現,轉置器知道怎么處理大多數單詞的單復數變換,甚至很多不規則的變換方式: ``` >> pluralize(2, "woman") => "2 women" >> pluralize(3, "erratum") => "3 errata" ``` 所以,使用 `pluralize` 方法后,如下的代碼: ``` <%= pluralize(@user.errors.count, "error") %> ``` 返回值是 `"0 errors"`、`"1 error"` 或 `"2 errors"` 等,單復數形式取決于錯誤的數量。這樣可以避免出現類似 `"1 errors"` 這種低級的錯誤(這是網絡中常見的錯誤之一)。 注意,[代碼清單 7.19](#listing-errors-partial) 還添加了一個 CSS ID,`error_explanation`,可用來樣式化錯誤消息。([5.1.2 節](chapter5.html#bootstrap-and-custom-css)介紹過,CSS 中以 `#` 開頭的規則是用來給 ID 添加樣式的。)出錯時,Rails 還會自動把有錯誤的字段包含在一個 CSS 類為 `field_with_errors` 的 `div` 元素中。我們可以利用這些 ID 和類為錯誤消息添加樣式,所需的 SCSS 如[代碼清單 7.20](#listing-error-messages-css) 所示。在這段代碼中,使用 Sass 的 `@extend` 函數引入了 Bootstrap 中的 `has-error` 類。 ##### 代碼清單 7.20:錯誤消息的樣式 app/assets/stylesheets/custom.css.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; } } ``` 添加[代碼清單 7.18](#listing-f-error-messages) 和[代碼清單 7.19](#listing-errors-partial) 中的代碼,以及[代碼清單 7.20](#listing-error-messages-css) 中的 SCSS 之后,提交無效的注冊信息后,會顯示一些有用的錯誤消息,如[圖 7.18](#fig-signup-error-messages) 所示。因為錯誤消息是由模型驗證生成的,所以如果以后修改了驗證規則,例如電子郵件地址的格式,或者密碼的最短長度,錯誤消息會自動變化。(注意,因為我們添加了存在性驗證,而且 `has_secure_password` 方法會驗證是否有密碼(密碼是否為 `nil`),所以,如果用戶沒有輸入密碼,目前會出現重復的錯誤消息。我們可以直接處理錯誤消息,去掉重復的消息,不過,[9.1.4 節](chapter9.html#successful-edits)添加 `allow_nil: true` 之后,會自動解決這個問題。) ![signup error messages 3rd edition](https://box.kancloud.cn/2016-05-11_5732bd1540109.png)圖 7.18:注冊失敗后顯示的錯誤消息 ## 7.3.4 注冊失敗的測試 在沒有完全支持測試的強大 Web 框架出現以前,開發者不得不自己動手測試表單。例如,為了測試注冊頁面,我們要在瀏覽器中訪問這個頁面,然后分別提交無效和有效的注冊信息,檢查各種情況下應用的表現是否正常。而且,每次修改應用后都要重復這個痛苦又容易出錯的過程。 幸好,使用 Rails 可以編寫測試,自動測試表單。這一節,我們要編寫測試,確認在表單中提交無效的數據時表現正確。[7.4.4 節](#a-test-for-valid-submission)會編寫提交有效數據時的測試。 首先,我們要為用戶注冊功能生成一個集成測試文件,這個文件名為 `users_signup`(沿用使用復數命名資源名的約定): ``` $ rails generate integration_test users_signup invoke test_unit create test/integration/users_signup_test.rb ``` ([7.4.4 節](#a-test-for-valid-submission)測試注冊成功時也使用這個文件。) 測試的主要目的是,確認點擊注冊按鈕提交無效數據后,不會創建新用戶。(對錯誤消息的測試留作[7.7 節](#sign-up-exercises)。)方法是檢測用戶的數量。測試會使用每個 Active Record 類(包括 `User` 類)都能使用的 `count` 方法: ``` $ rails console >> User.count => 0 ``` 現在 `User.count` 的返回值是 `0`,因為我們在 [7.2 節](#signup-form)開頭還原了數據庫。和 [5.3.4 節](chapter5.html#layout-link-tests)一樣,我們要使用 `assert_select` 測試相應頁面中的 HTML 元素。注意,只能測試以后基本不會修改的元素。 首先,我們使用 `get` 方法訪問注冊頁面: ``` get signup_path ``` 為了測試表單提交后的狀態,我們要向 `users_path` 發起 `POST` 請求([表 7.1](#table-restful-users))。這個操作可以使用 `post` 方法完成: ``` assert_no_difference 'User.count' do post users_path, user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } end ``` 這里用到了 `create` 動作中傳給 `User.new` 的 `params[:user]` 哈希([代碼清單 7.24](#listing-signup-flash))。我們把 `post` 方法放在 `assert_no_difference` 方法的塊中,并把 `assert_no_difference` 方法的參數設為字符串 `'User.count'`。執行這段代碼時,會比較塊中的代碼執行前后 `User.count` 的值。這段代碼相當于先記錄用戶數量,然后在 `post` 請求中發送數據,再確認用戶的數量沒變,如下所示: ``` before_count = User.count post users_path, ... after_count = User.count assert_equal before_count, after_count ``` 雖然這兩種方式的作用相同,但使用 `assert_no_difference` 更簡潔,而且更符合 Ruby 的習慣用法。 把上述代碼放在一起,寫出的測試如[代碼清單 7.21](#listing-a-test-for-invalid-submission) 所示。在測試中,我們還調用了 `assert_template` 方法,檢查提交失敗后是否會重新渲染 `new` 動作。檢查錯誤消息的測試留作練習,參見 [7.7 節](#sign-up-exercises)。 ##### 代碼清單 7.21:注冊失敗的測試 GREEN test/integration/users_signup_test.rb ``` require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } end assert_template 'users/new' end end ``` 因為在編寫集成測試之前已經寫好了應用代碼,所以測試組件應該能通過: ##### 代碼清單 7.22:**GREEN** ``` $ bundle exec rake test ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看