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

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 9.1 更新用戶 編輯用戶信息的方法和創建新用戶差不多(參見[第 7 章](chapter7.html#sign-up)),創建新用戶的頁面在 `new` 動作中處理,而編輯用戶的頁面在 `edit` 動作中處理;創建用戶的過程在 `create` 動作中處理 `POST` 請求,編輯用戶要在 `update` 動作中處理 `PATCH` 請求([旁注 3.2](chapter3.html#aside-get-etc))。二者之間最大的區別是,任何人都可以注冊,但只有當前用戶才能更新自己的信息。我們可以使用[第 8 章](chapter8.html#log-in-log-out)實現的認證機制,通過“事前過濾器”(before filter)實現訪問限制。 開始實現之前,我們先切換到 `updating-users` 主題分支: ``` $ git checkout master $ git checkout -b updating-users ``` ## 9.1.1 編輯表單 我們先來創建編輯表單,構思圖如[圖 9.1](#fig-edit-user-mockup)。[[1](#fn-1)]要把這個構思圖轉換成可以使用的頁面,我們既要編寫用戶控制器的 `edit` 動作,也要創建編輯用戶的視圖。我們先來編寫 `edit` 動作。在 `edit` 動作中我們要從數據庫中讀取相應的用戶。由[表 7.1](chapter7.html#table-restful-users) 得知,用戶的編輯頁面地址是 /users/1/edit(假設用戶的 ID 是 1)。我們知道用戶的 ID 可以使用 `params[:id]` 獲取,那么就可以使用[代碼清單 9.1](#listing-initial-edit-action) 中的代碼查找用戶。 ##### 代碼清單 9.1:用戶控制器的 `edit` 動作 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(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end def edit @user = User.find(params[:id]) end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end ``` ![edit user mockup bootstrap](https://box.kancloud.cn/2016-05-11_5733305accabe.png)圖 9.1:用戶編輯頁面的構思圖 用戶編輯頁面的視圖(要手動創建這個文件)如[代碼清單 9.2](#listing-user-edit-view) 所示。注意,這個視圖和[代碼清單 7.13](chapter7.html#listing-signup-form) 中新建用戶的視圖很相似,有很多重復的代碼,所以可以重構,把共用的代碼放到局部視圖中,這個任務留作練習([9.6 節](#updating-showing-and-deleting-users-exercises))。 ##### 代碼清單 9.2:用戶編輯頁面的視圖 app/views/users/edit.html.erb ``` <% provide(:title, "Edit user") %> <h1>Update your profile</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 "Save changes", class: "btn btn-primary" %> <% end %> <div class="gravatar_edit"> <%= gravatar_for @user %> <a href="http://gravatar.com/emails" target="_blank">change</a> </div> </div> </div> ``` 這里再次用到了 [7.3.3 節](chapter7.html#signup-error-messages)創建的 `error_messages` 局部視圖。順便說一下,修改 Gravatar 頭像的鏈接用到了 `target="_blank"`,目的是在新窗口或選項卡中打開這個網頁。鏈接到第三方網站時一般都會這么做。 [代碼清單 9.1](#listing-initial-edit-action) 中定義了 `@user` 實例變量,所以編輯頁面可以正確渲染,如[圖 9.2](#fig-edit-page) 所示。從“Name”和“Email”字段可以看出,Rails 會自動使用 `@user` 變量的屬性值填寫相應的字段。 ![edit page 3rd edition](https://box.kancloud.cn/2016-05-11_5733305b01c14.png)圖 9.2:編輯頁面初始版本,名字和電子郵件地址自動填入了值 查看用戶編輯頁面的 HTML 源碼,會看到預期的表單標簽,如[代碼清單 9.3](#listing-edit-form-html) 所示(某些細節可能不同)。 ##### 代碼清單 9.3:[代碼清單 9.2](#listing-user-edit-view) 定義的編輯表單生成的 HTML ``` <form accept-charset="UTF-8" action="/users/1" class="edit_user" id="edit_user_1" method="post"> <input name="_method" type="hidden" value="patch" /> . . . </form> ``` 留意一下這個隱藏字段: ``` <input name="_method" type="hidden" value="patch" /> ``` 因為瀏覽器并不支持發送 `PATCH` 請求([表 7.1](chapter7.html#table-restful-users) 中的 REST 動作要用),所以 Rails 在 `POST` 請求中使用這個隱藏字段偽造了一個 `PATCH` 請求。[[2](#fn-2)] 還有一個細節需要注意一下,[代碼清單 9.2](#listing-user-edit-view) 和[代碼清單 7.13](chapter7.html#listing-signup-form) 都使用了相同的 `form_for(@user)` 來構建表單,那么 Rails 是怎么知道創建新用戶要發送 `POST` 請求,而編輯用戶時要發送 `PATCH` 請求的呢?這個問題的答案是,通過 Active Record 提供的 `new_record?` 方法檢測用戶是新創建的還是已經存在于數據庫中: ``` $ rails console >> User.new.new_record? => true >> User.first.new_record? => false ``` 所以使用 `form_for(@user)` 構建表單時,如果 `@user.new_record?` 返回 `true`,發送 `POST` 請求,否則發送 `PATCH` 請求。 最后,我們要把導航中指向編輯用戶頁面的鏈接換成真實的地址。很簡單,我們直接使用[表 7.1](chapter7.html#table-restful-users) 中列出的 `edit_user_path` 具名路由,并把參數設為[代碼清單 8.36](chapter8.html#listing-persistent-current-user) 中定義的 `current_user` 輔助方法: ``` <%= link_to "Settings", edit_user_path(current_user) %> ``` 完整的視圖如[代碼清單 9.4](#listing-settings-link) 所示。 ##### 代碼清單 9.4:在網站布局中設置“Settings”鏈接的地址 app/views/layouts/_header.html.erb ``` <header class="navbar navbar-fixed-top navbar-inverse"> <div class="container"> <%= link_to "sample app", root_path, id: "logo" %> <nav> <ul class="nav navbar-nav navbar-right"> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Help", help_path %></li> <% if logged_in? %> <li><%= link_to "Users", '#' %></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Account <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><%= link_to "Profile", current_user %></li> <li><%= link_to "Settings", edit_user_path(current_user) %></li> <li class="divider"></li> <li> <%= link_to "Log out", logout_path, method: "delete" %> </li> </ul> </li> <% else %> <li><%= link_to "Log in", login_path %></li> <% end %> </ul> </nav> </div> </header> ``` ## 9.1.2 編輯失敗 本節我們要處理編輯失敗的情況,過程和處理注冊失敗差不多([7.3 節](chapter7.html#unsuccessful-signups))。我們要先定義 `update` 動作,把提交的 `params` 哈希傳給 `update_attributes` 方法([6.1.5 節](chapter6.html#updating-user-objects)),更新用戶,如[代碼清單 9.5](#listing-user-update-action-unsuccessful) 所示。如果提交的數據無效,更新操作會返回 `false`,由 `else` 分支處理,重新渲染編輯頁面。我們之前用過類似的處理方式,代碼結構和第一個版本的 `create` 動作類似([代碼清單 7.16](chapter7.html#listing-first-create-action))。 ##### 代碼清單 9.5:`update` 動作初始版本 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(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end def edit @user = User.find(params[:id]) end def update @user = User.find(params[:id]) if @user.update_attributes(user_params) # 處理更新成功的情況 else render 'edit' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end ``` 注意在調用 `update_attributes` 方法時指定的 `user_params` 參數,這種用法是“健壯參數”(strong parameter),可以避免批量賦值帶來的安全隱患(參見 [7.3.2 節](chapter7.html#strong-parameters))。 因為用戶模型中定義了驗證規則,而且[代碼清單 9.2](#listing-user-edit-view) 中渲染了錯誤消息局部視圖,所以提交無效信息后會顯示一些有用的錯誤消息,如[圖 9.3](#fig-buggy-edit-with-invalid-information) 所示。 ![edit with invalid information 3rd edition](https://box.kancloud.cn/2016-05-11_5733305b1ea67.png)圖 9.3:提交編輯表單后顯示的錯誤消息 ## 9.1.3 編輯失敗的測試 [9.1.2 節](#unsuccessful-edits)結束時編輯表單已經可以使用,按照[旁注 3.3](chapter3.html#aside-when-to-test) 中的測試指導方針,現在我們要編寫集成測試捕獲回歸。和之前一樣,首先要生成一個集成測試文件: ``` $ rails generate integration_test users_edit invoke test_unit create test/integration/users_edit_test.rb ``` 然后為編輯失敗編寫一個簡單的測試,如[代碼清單 9.6](#listing-unsuccessful-edit-test) 所示。在這段測試中,我們檢查提交無效信息后會重新渲染編輯模板,以此確認表現是否正確。注意,這里使用 `patch` 方法發起 `PATCH` 請求,用法與 `get`、`post` 和 `delete` 類似。 ##### 代碼清單 9.6:編輯失敗的測試 GREEN test/integration/users_edit_test.rb ``` require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end test "unsuccessful edit" do get edit_user_path(@user) patch user_path(@user), user: { name: '', email: 'foo@invalid', password: 'foo', password_confirmation: 'bar' } assert_template 'users/edit' end end ``` 此時,測試組件應該可以通過: ##### 代碼清單 9.7:**GREEN** ``` $ bundle exec rake test ``` ## 9.1.4 編輯成功(使用 TDD) 現在我們要讓編輯表單能正常使用。編輯頭像的功能已經有了,因為我們把上傳頭像的操作交由 Gravatar 處理,如需更換頭像,點擊[圖 9.2](#fig-edit-page) 中的“change”鏈接就可以了,如[圖 9.4](#fig-gravatar-cropper) 所示。下面我們來實現編輯其他信息的功能。 ![gravatar cropper](https://box.kancloud.cn/2016-05-11_5733305b3ae6b.png)圖 9.4:Gravatar 的圖片剪切界面,上傳了一個[帥哥](http://www.michaelhartl.com/)的圖片 上手測試后,你可能會發現,編寫應用代碼之前編寫測試比之后再寫更有用。針對現在這種情況,我們要編寫的是“驗收測試”(acceptance test),由測試的結果決定某個功能是否完成。為了演示如何編寫驗收測試,我們要使用測試驅動開發技術完成用戶編輯功能。 我們要編寫類似[代碼清單 9.6](#listing-unsuccessful-edit-test) 中的測試,確認更新用戶的操作表現正確,只不過這一次我們會提交有效的信息。然后檢查顯示了閃現消息,而且成功重定向到了用戶的資料頁面,同時還要確認數據庫中保存的用戶信息也正確更新了。這個測試如[代碼清單 9.8](#listing-successful-edit-test) 所示。注意,在[代碼清單 9.8](#listing-successful-edit-test) 中,密碼和密碼確認都為空值,因為修改用戶名和電子郵件地址時并不想修改密碼。還要注意,我們使用 `@user.reload`([6.1.5 節](chapter6.html#updating-user-objects)首次用到)重新加載數據庫中存儲的值,以此確認成功更新了信息。(新手很容易忘記這個操作,這就是為什么必須要有一定的經驗才能編寫有效的驗收測試(推及到 TDD)的原因。) ##### 代碼清單 9.8:編輯成功的測試 RED test/integration/users_edit_test.rb ``` require 'test_helper' class UsersEditTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "successful edit" do get edit_user_path(@user) name = "Foo Bar" email = "foo@bar.com" patch user_path(@user), user: { name: name, email: email, password: "", password_confirmation: "" } assert_not flash.empty? assert_redirected_to @user @user.reload assert_equal @user.name, name assert_equal @user.email, email end end ``` 要讓[代碼清單 9.8](#listing-successful-edit-test) 中的測試通過,我們可以參照最終版 `create` 動作([代碼清單 8.22](chapter8.html#listing-login-upon-signup))來編寫 `update` 動作,如[代碼清單 9.9](#listing-user-update-action) 所示。 ##### 代碼清單 9.9:用戶控制器的 `update` 動作 RED app/controllers/users_controller.rb ``` class UsersController < ApplicationController . . . def update @user = User.find(params[:id]) if @user.update_attributes(user_params) flash[:success] = "Profile updated" redirect_to @user else render 'edit' end end . . . end ``` 如[代碼清單 9.9](#listing-user-update-action) 的標題所示,測試組件無法通過,因為密碼長度驗證([代碼清單 6.39](chapter6.html#listing-password-implementation))失敗了,這是因為[代碼清單 9.8](#listing-successful-edit-test) 中密碼和密碼確認都是空值。為了讓測試通過,我們要在密碼為空值時特殊處理最短長度驗證,方法是把 `allow_nil: true` 參數傳給 `validates` 方法,如[代碼清單 9.10](#listing-allow-blank-password) 所示。 ##### 代碼清單 9.10:更新時允許密碼為空 GREEN app/models/user.rb ``` class User < ActiveRecord::Base attr_accessor :remember_token before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 } format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 }, allow_nil: true . . . end ``` 你可能擔心這么改用戶注冊時可以把密碼設為空值,其實不然,[6.3.3 節](chapter6.html#minimum-password-standards)說過,創建對象時,`has_secure_password` 會執行存在性驗證,捕獲密碼為 `nil` 的情況。(密碼為 `nil` 時能通過存在性驗證,可是會被 `has_secure_password` 方法的驗證捕獲,因此修正了 [7.3.3 節](chapter7.html#signup-error-messages)提到的錯誤消息重復問題。) 至此,用戶編輯頁面應該可以正常使用了,如[圖 9.5](#fig-edit-form-working) 所示。你也可以運行測試組件確認一下,應該可以通過: ##### 代碼清單 9.11:**GREEN** ``` $ bundle exec rake test ``` ![edit form working](https://box.kancloud.cn/2016-05-11_5733305b5d69f.png)圖 9.5:編輯成功后顯示的頁面
                  <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>

                              哎呀哎呀视频在线观看