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

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 8.2 登錄 登錄表單已經可以處理無效提交,下一步要正確處理有效提交,登入用戶。本節通過臨時會話讓用戶登錄,瀏覽器關閉后會話自動失效。[8.4 節](#remember-me)會實現持久會話,即便瀏覽器關閉,依然處于登錄狀態。 實現會話的過程中要定義很多相關的函數,而且要在多個控制器和視圖中使用。[4.2.5 節](chapter4.html#back-to-the-title-helper)說過,Ruby 支持使用“模塊”把這些函數集中放在一處。Rails 生成器很人性化,生成會話控制器時([8.1.1 節](#sessions-controller))自動生成了一個會話輔助方法模塊。而且,其中的輔助方法會自動引入 Rails 視圖。如果在控制器的基類(`ApplicationController`)中引入輔助方法模塊,還可以在控制器中使用,如[代碼清單 8.11](#listing-sessions-helper-include) 所示。 ##### 代碼清單 8.11:在 `ApplicationController` 中引入會話輔助方法模塊 app/controllers/application_controller.rb ``` class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper end ``` 做好這些基礎工作后,現在可以開始編寫代碼登入用戶了。 ## 8.2.1 `log_in` 方法 有 Rails 提供的 `session` 方法協助,登入用戶很簡單。(`session` 方法和 [8.1.1 節](#sessions-controller)生成的會話控制器沒有關系。)我們可以把 `session` 視作一個哈希,可以按照下面的方式賦值: ``` session[:user_id] = user.id ``` 這么做會在用戶的瀏覽器中創建一個臨時 cookie,內容是加密后的用戶 ID。在后續的請求中,可以使用 `session[:user_id]` 取回這個 ID。[8.4 節](#remember-me)使用的 `cookies` 方法創建的是持久 cookie,而 `session` 方法創建的是臨時會話,瀏覽器關閉后立即失效。 我們想在多個不同的地方使用這個登錄方式,所以在會話輔助方法模塊中定義一個名為 `log_in` 的方法,如[代碼清單 8.12](#listing-log-in-function) 所示。 ##### 代碼清單 8.12:`log_in` 方法 app/helpers/sessions_helper.rb ``` module SessionsHelper # 登入指定的用戶 def log_in(user) session[:user_id] = user.id end end ``` `session` 方法創建的臨時 cookie 會自動加密,所以[代碼清單 8.12](#listing-log-in-function) 中的代碼是安全的,攻擊者無法使用會話中的信息以該用戶的身份登錄。不過,只有 `session` 方法創建的臨時 cookie 是這樣,`cookies` 方法創建的持久 cookie 則有可能會受到“會話劫持”(session hijacking)攻擊。所以在 [8.4 節](#remember-me)我們會小心處理存入用戶瀏覽器中的信息。 定義好 `log_in` 方法后,我們可以完成會話控制器中的 `create` 動作了——登入用戶,然后重定向到用戶的資料頁面,如[代碼清單 8.13](#listing-log-in-success) 所示。[[4](#fn-4)] ##### 代碼清單 8.13:登入用戶 app/controllers/sessions_controller.rb ``` class SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy end end ``` 注意簡潔的重定向代碼 ``` redirect_to user ``` 我們在 [7.4.1 節](chapter7.html#the-finished-signup-form)見過。Rails 會自動把地址轉換成用戶資料頁的地址: ``` user_url(user) ``` 定義好 `create` 動作后,[代碼清單 8.2](#listing-login-form) 中的登錄表單就可以使用了。不過從應用的外觀上看不出什么區別,除非直接查看瀏覽器中的會話,否則沒有方法判斷用戶是否已經登錄。[8.2.2 節](#current-user)會使用會話中的用戶 ID 從數據庫中取回當前用戶,做些視覺上的變化。[8.2.3 節](#changing-the-layout-links)會修改網站布局中的鏈接,還會添加一個指向當前用戶資料頁面的鏈接。 ## 8.2.2 當前用戶 把用戶 ID 安全地存儲在臨時會話中之后,在后續的請求中可以將其讀取出來。我們要定義一個名為 `current_user` 的方法,從數據庫中取出用戶 ID 對應的用戶。`current_user` 方法的作用是編寫類似下面的代碼: ``` <%= current_user.name %> ``` 或是: ``` redirect_to current_user ``` 查找用戶的方法之一是使用 `find` 方法,在用戶資料頁面就是這么做的([代碼清單 7.5](chapter7.html#listing-user-show-action)): ``` User.find(session[:user_id]) ``` [6.1.4 節](chapter6.html#finding-user-objects)說過,如果用戶 ID 不存在,`find` 方法會拋出異常。在用戶的資料頁面可以使用這種表現,因為必須有相應的用戶才能顯示他的信息。但 `session[:user_id]` 的值經常是 `nil`(表示用戶未登錄),所以我們要使用 `create` 動作中通過電子郵件地址查找用戶的 `find_by` 方法,通過 `id` 查找用戶: ``` User.find_by(id: session[:user_id]) ``` 如果 ID 無效,`find_by` 方法返回 `nil`,而不會拋出異常。 因此,我們可以按照下面的方式定義 `current_user` 方法: ``` def current_user User.find_by(id: session[:user_id]) end ``` 這樣定義應該可以,不過如果頁面中多次調用 `current_user`,就會多次查詢數據庫。所以,我們要使用一種 Ruby 習慣寫法,把 `User.find_by` 的結果存儲在實例變量中,只在第一次調用時查詢數據庫,后續再調用直接返回實例變量中存儲的值:[[5](#fn-5)] ``` if @current_user.nil? @current_user = User.find_by(id: session[:user_id]) else @current_user end ``` 使用 [4.2.3 節](chapter4.html#objects-and-message-passing)中介紹的“或”操作符 `||`,可以把這段代碼改寫成: ``` @current_user = @current_user || User.find_by(id: session[:user_id]) ``` `User` 對象是真值,所以僅當 `@current_user` 沒有賦值時才會執行 `find_by` 方法。 上述代碼雖然可以使用,但并不符合 Ruby 的習慣。`@current_user` 賦值語句的正確寫法是這樣: ``` @current_user ||= User.find_by(id: session[:user_id]) ``` 這種寫法用到了容易讓人困惑的 `||=`(或等)操作符,參見[旁注 8.1](#aside-or-equals) 中的說明。 ##### 旁注 8.1:`||=` 操作符簡介 `||=`(或等)賦值操作符在 Ruby 中常用,因此有追求的 Rails 開發者要學會使用。初學時可能會覺得 `||=` 很神秘,不過和其他操作符對比之后,你會發現也不難理解。 我們先來看一下常見的變量自增一賦值: ``` x = x + 1 ``` 很多編程語言都為這種操作提供了簡化的操作符,在 Ruby 中(C、C++、Perl、Python、Java 等也可以),可以寫成下面這樣: ``` x += 1 ``` 其他操作符也有類似的簡化形式: ``` $ rails console >> x = 1 => 1 >> x += 1 => 2 >> x *= 3 => 6 >> x -= 8 => -2 >> x /= 2 => -1 ``` 通過上面的例子可以得知,`x = x O y` 和 `x O=y` 是等效的,其中 `O` 表示操作符。 在 Ruby 中還經常會遇到這種情況,如果變量的值為 `nil` 則給它賦值,否則就不改變這個變量的值。我們可以使用 [4.2.3 節](chapter4.html#objects-and-message-passing)介紹的或操作符(`||`)編寫下面的代碼: ``` >> @foo => nil >> @foo = @foo || "bar" => "bar" >> @foo = @foo || "baz" => "bar" ``` 因為 `nil` 是“假值”,所以第一個賦值語句等同于 `nil || "bar"`,得到的結果是 `"bar"`。同樣,第二個賦值操作等同于 `"bar" || "baz"`,得到的結果還是 `"bar"`。這是因為除了 `nil` 和 `false` 之外,其他值都是“真值”,而如果第一個表達式的值是真值,`||` 會終止執行。(或操作的執行順序從左至右,只要出現真值就會終止語句的執行,這種方式叫“短路計算”(short-circuit evaluation)。) 和前面的控制臺會話對比之后,我們發現 `@foo = @foo || "bar"` 符合 `x = x O y` 形式,其中 `||` 就是 `O`: ``` x = x + 1 -> x += 1 x = x * 3 -> x *= 3 x = x - 8 -> x -= 8 x = x / 2 -> x /= 2 @foo = @foo || "bar" -> @foo ||= "bar" ``` 因此,`@foo = @foo || "bar"` 和 `@foo ||= "bar"` 兩種寫法是等效的。在獲取當前用戶時,建議使用下面的寫法: ``` @current_user ||= User.find_by(id: session[:user_id]) ``` 不難理解吧![[6](#fn-6)] 綜上所述,`current_user` 方法更簡潔的定義方式如[代碼清單 8.14](#listing-current-user) 所示。 ##### 代碼清單 8.14:在會話中查找當前用戶 app/helpers/sessions_helper.rb ``` module SessionsHelper # 登入指定的用戶 def log_in(user) session[:user_id] = user.id end # 返回當前登錄的用戶(如果有的話) def current_user @current_user ||= User.find_by(id: session[:user_id]) end end ``` 定義好 `current_user` 之后,現在可以根據用戶的登錄狀態修改應用的布局了。 ## 8.2.3 修改布局中的鏈接 實現登錄功能后,我們要根據登錄狀態修改布局中的鏈接。具體而言,我們要添加退出鏈接、用戶設置頁面的鏈接、用戶列表頁面的鏈接和當前用戶的資料頁面鏈接,構思圖如[圖 8.7](#fig-login-success-mockup) 所示。[[7](#fn-7)]注意,退出鏈接和資料頁面的鏈接在“Account”(賬戶)下拉菜單中。使用 Bootstrap 實現下拉菜單的方法參見[代碼清單 8.16](#listing-layout-login-logout-links)。 ![login success mockup](https://box.kancloud.cn/2016-05-11_57333052b0130.png)圖 8.7:成功登錄后顯示的資料頁面構思圖 此時,在現實開發中,我會考慮編寫集成測試檢測上面規劃的行為。我在[旁注 3.3](chapter3.html#aside-when-to-test) 中說過,當你熟練掌握 Rails 的測試工具后,會傾向于先寫測試。但這個測試涉及到一些新知識,所以最好在專門的一節中編寫([8.2.4 節](#testing-layout-changes))。 修改網站布局中的鏈接時要在 ERb 中使用 `if-else` 語句,用戶登錄時顯示一組鏈接,未登錄時顯示另一組鏈接: ``` <% if logged_in? %> # 登錄用戶看到的鏈接 <% else %> # 未登錄用戶看到的鏈接 <% end %> ``` 為了編寫這種代碼,我們需要定義 `logged_in?` 方法,返回布爾值。 用戶登錄后,當前用戶存儲在會話中,即 `current_user` 不是 `nil`。檢測會話中有沒有當前用戶要使用“非”操作符([4.2.3 節](chapter4.html#objects-and-message-passing))。“非”操作符寫做 `!`,經常讀作“bang”。`logged_in?` 方法的定義如[代碼清單 8.15](#listing-logged-in-p) 所示。 ##### 代碼清單 8.15:`logged_in?` 輔助方法 app/helpers/sessions_helper.rb ``` module SessionsHelper # 登入指定的用戶 def log_in(user) session[:user_id] = user.id end # 返回當前登錄的用戶(如果有的話) def current_user @current_user ||= User.find_by(id: session[:user_id]) end # 如果用戶已登錄,返回 true,否則返回 false def logged_in? !current_user.nil? end end ``` 定義好 `logged_in?` 方法之后,可以修改用戶登錄后顯示的鏈接了。我們要添加四個新鏈接,其中兩個鏈接的地址先使用占位符,[第 9 章](chapter9.html#updating-showing-and-deleting-users)會換成真正的地址: ``` <%= link_to "Users", '#' %> <%= link_to "Settings", '#' %> ``` 退出鏈接使用[代碼清單 8.1](#listing-sessions-resource) 中定義的退出頁面地址: ``` <%= link_to "Log out", logout_path, method: "delete" %> ``` 注意,退出鏈接中指定了哈希參數,指明這個鏈接發送的是 HTTP `DELETE` 請求。[[8](#fn-8)]我們還要添加資料頁面的鏈接: ``` <%= link_to "Profile", current_user %> ``` 這個鏈接可以寫成: ``` <%= link_to "Profile", user_path(current_user) %> ``` 和之前一樣,我們可以直接鏈接到用戶對象,Rails 會自動把 `current_user` 轉換成 `user_path(current_user)`。最后,如果用戶未登錄,我們要添加一個鏈接,使用[代碼清單 8.1](#listing-sessions-resource) 中定義的登錄地址,鏈接到登錄頁面: ``` <%= link_to "Log in", login_path %> ``` 把這些鏈接都放到頭部局部視圖中,得到的視圖如[代碼清單 8.16](#listing-layout-login-logout-links) 所示。 ##### 代碼清單 8.16:修改布局中的鏈接 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", '#' %></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> ``` 除了在布局中添加新鏈接之外,[代碼清單 8.16](#listing-layout-login-logout-links) 還借助 Bootstrap 實現了下拉菜單。[[9](#fn-9)]注意這段代碼中使用的幾個 Bootstrap CSS 類:`dropdown`,`dropdown-menu` 等。為了讓下拉菜單生效,我們要在 `application.js`(Asset Pipeline 的一部分)中引入 Bootstrap 提供的 JavaScript 庫,如[代碼清單 8.17](#listing-bootstrap-js) 所示。 ##### 代碼清單 8.17:在 `application.js` 中引入 Bootstrap JavaScript 庫 app/assets/javascripts/application.js ``` //= require jquery //= require jquery_ujs //= require bootstrap //= require turbolinks //= require_tree . ``` 現在,你應該訪問登錄頁面,然后使用有效賬戶登錄——這樣足以測試前三節編寫的代碼表現是否正常。[[10](#fn-10)]添加[代碼清單 8.16](#listing-layout-login-logout-links) 和[代碼清單 8.17](#listing-bootstrap-js) 中的代碼后,應該能看到下拉菜單和只有已登錄用戶才能看到的鏈接,如[圖 8.8](#fig-profile-with-logout-link) 所示。如果關閉瀏覽器,還能確認應用確實忘了登錄狀態,必須再次登錄才能看到上述改動。 ![profile with logout link 3rd edition](https://box.kancloud.cn/2016-05-11_57333052d80c3.png)圖 8.8:用戶登錄后看到了新添加的鏈接和下拉菜單 ## 8.2.4 測試布局中的變化 我們自己動手驗證了成功登錄后應用的表現正常,在繼續之前,還要編寫集成測試檢查這些行為,以及捕獲回歸。我們要在[代碼清單 8.7](#listing-flash-persistence-test)的基礎上,再添加一些測試,檢查下面的操作步驟: 1. 訪問登錄頁面; 2. 通過 `post` 請求發送有效的登錄信息; 3. 確認登錄鏈接消失了; 4. 確認出現了退出鏈接; 5. 確認出現了資料頁面鏈接。 為了檢查這些變化,在測試中要登入已經注冊的用戶,也就是說數據庫中必須有一個用戶。Rails 默認使用“固件”實現這種需求。固件是一種組織數據的方式,這些數據會載入測試數據庫。[6.2.5 節](chapter6.html#uniqueness-validation)刪除了默認生成的固件([代碼清單 6.30](chapter6.html#listing-empty-fixtures)),目的是讓檢查電子郵件地址的測試通過。現在,我們要在這個空文件中加入自定義的固件。 目前,我們只需要一個用戶,它的名字和電子郵件地址應該是有效的。因為我們要登入這個用戶,所以還要提供正確的密碼,和提交給會話控制器中 `create` 動作的密碼比較。參照[圖 6.7](chapter6.html#fig-user-model-password-digest) 中的數據模型,可以看出,我們要在用戶固件中定義 `password_digest` 屬性。我們會定義 `digest` 方法計算這個屬性的值。 [6.3.1 節](chapter6.html#a-hashed-password)說過,密碼摘要使用 bcrypt 生成(通過 `has_secure_password` 方法),所以固件中的密碼摘要也要使用這種方法生成。查看[安全密碼的源碼](https://github.com/rails/rails/blob/master/activemodel/lib/active_model/secure_password.rb)后,我們發現生成摘要的方法是: ``` BCrypt::Password.create(string, cost: cost) ``` 其中,`string` 是要計算哈希值的字符串;`cost` 是“耗時因子”,決定計算哈希值時消耗的資源。耗時因子的值越大,由哈希值破解出原密碼的難度越大。這個值對生產環境的安全防護很重要,但在測試中我們希望 `digest` 方法的執行速度越快越好。安全密碼的源碼中還有這么一行代碼: ``` cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost ``` 這行代碼相當難懂,你無須完全理解,它的作用是嚴格實現前面的分析:在測試中耗時因子使用最小值,在生產環境則使用普通(最大)值。([8.4.5 節](chapter8.html#remember-me-checkbox)會深入介紹奇怪的 `?-:` 寫法。) `digest` 方法可以放在幾個不同的地方,但 [8.4.1 節](#remember-token-and-digest)會在用戶模型中使用,所以建議放在 `user.rb` 中。因為計算摘要時不用獲取用戶對象,所以我們要把 `digest` 方法附在 `User` 類上,也就是定義為類方法([4.4.1 節](chapter4.html#constructors)簡要介紹過)。結果如[代碼清單 8.18](#listing-digest-method) 所示。 ##### 代碼清單 8.18:定義固件中要使用的 `digest` 方法 app/models/user.rb ``` class User < ActiveRecord::Base 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 } # 返回指定字符串的哈希摘要 def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end end ``` 定義好 `digest` 方法后,我們可以創建一個有效的用戶固件了,如[代碼清單 8.19](#listing-real-user-fixture) 所示。 ##### 代碼清單 8.19:測試用戶登錄所需的固件 test/fixtures/users.yml ``` michael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %> ``` 特別注意一下,固件中可以使用嵌入式 Ruby。因此,我們可以使用 ``` <%= User.digest('password') %> ``` 生成測試用戶正確的密碼摘要。 我們雖然定義了 `has_secure_password` 所需的 `password_digest` 屬性,但有時也需要使用密碼的原始值。可是,在固件中無法實現,如果在[代碼清單 8.19](#listing-real-user-fixture) 中添加 `password` 屬性,Rails 會提示數據庫中沒有這個列(確實沒有)。所以,我們約定固件中所有用戶的密碼都一樣,即 `'password'`。 創建了一個有效用戶固件后,在測試中可以使用下面的方式獲取這個用戶: ``` user = users(:michael) ``` 其中,`users` 對應固件文件 `users.yml` 的文件名,`:michael` 是[代碼清單 8.19](#listing-real-user-fixture) 中定義的用戶。 定義好用戶固件之后,現在可以把本節開頭列出的操作步驟轉換成代碼了,如[代碼清單 8.20](#listing-user-login-test-valid-information) 所示。(注意,這段代碼中的 `get` 和 `post` 兩步嚴格來說沒有關系,其實向控制器發起 `POST` 請求之前沒必要向登錄頁面發起 `GET` 請求。我之所以加入這一步是為了明確表明操作步驟,以及確認渲染登錄表單時沒有錯誤。) ##### 代碼清單 8.20:測試使用有效信息登錄的情況 GREEN test/integration/users_login_test.rb ``` require 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "login with valid information" do get login_path post login_path, session: { email: @user.email, password: 'password' } assert_redirected_to @user follow_redirect! assert_template 'users/show' assert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", logout_path assert_select "a[href=?]", user_path(@user) end end ``` 在這段代碼中,我們使用 `assert_redirected_to @user` 檢查重定向的地址是否正確;使用 `follow_redirect!` 訪問重定向的目標地址。還確認頁面中有零個登錄鏈接,從而確認登錄鏈接消失了: ``` assert_select "a[href=?]", login_path, count: 0 ``` `count: 0` 參數的目的是,告訴 `assert_select`,我們期望頁面中有零個匹配指定模式的鏈接。([代碼清單 5.25](chapter5.html#listing-layout-links-test)中使用的是 `count: 2`,指定必須有兩個匹配模式的鏈接。) 因為應用代碼已經能正常運行,所以這個測試應該可以通過: ##### 代碼清單 8.21:**GREEN** ``` $ bundle exec rake test TEST=test/integration/users_login_test.rb \ > TESTOPTS="--name test_login_with_valid_information" ``` 上述命令說明了如何運行一個測試文件中的某個測試——使用如下參數,并指定測試的名字: ``` TESTOPTS="--name test_login_with_valid_information" ``` (測試的名字是使用下劃線把“test”和測試說明連接在一起。) ## 8.2.5 注冊后直接登錄 雖然現在基本完成了認證功能,但是新注冊的用戶可能還是會困惑,為什么注冊后沒有登錄呢。注冊后立即要求用戶登錄是很奇怪的,所以我們要在注冊的過程中自動登入用戶。為了實現這一功能,我們只需在用戶控制器的 `create` 動作中調用 `log_in` 方法,如[代碼清單 8.22](#listing-login-upon-signup) 所示。[[11](#fn-11)] ##### 代碼清單 8.22:注冊后登入用戶 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 private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end ``` 為了測試這個功能,我們可以在[代碼清單 7.26](chapter7.html#listing-a-test-for-valid-submission) 中添加一行代碼,檢查用戶是否已經登錄。我們可以定義一個 `is_logged_in?` 輔助方法,功能和[代碼清單 8.15](#listing-logged-in-p) 中的 `logged_in?` 方法一樣,如果(測試環境的)會話中有用戶的 ID 就返回 `true`,否則返回 `false`,如[代碼清單 8.23](#listing-test-helper-sessions) 所示。(我們不能像[代碼清單 8.15](#listing-logged-in-p) 那樣使用 `current_user`,因為在測試中不能使用 `current_user` 方法,但是可以使用 `session` 方法。)我們定義的方法不是 `logged_in?`,而是 `is_logged_in?`——測試輔助方法和會話輔助方法名字不一樣,以免混淆。[[12](#fn-12)] ##### 代碼清單 8.23:在測試中定義檢查登錄狀態的方法,返回布爾值 test/test_helper.rb ``` ENV['RAILS_ENV'] ||= 'test' . . . class ActiveSupport::TestCase fixtures :all # 如果用戶已登錄,返回 true def is_logged_in? !session[:user_id].nil? end end ``` 然后,我們可以使用[代碼清單 8.24](#listing-login-after-signup-test) 中的測試檢查注冊后用戶有沒有登錄。 ##### 代碼清單 8.24:測試注冊后有沒有登入用戶 GREEN test/integration/users_signup_test.rb ``` require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest . . . test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post_via_redirect users_path, user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } end assert_template 'users/show' assert is_logged_in? end end ``` 現在,測試組件應該可以通過: ##### 代碼清單 8.25:**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>

                              哎呀哎呀视频在线观看