ナビゲーションバーで各ページへの導線を作ろう
学習の目標
本章では、以下の内容を学習します。
- ユーザーのログイン状態に応じて表示を切り替えるナビゲーションバーを実装する
- 部分テンプレート(パーシャル)の作成と活用方法を習得する
- RSpecのシステムテストでログイン状態をシミュレートする方法を学ぶ
- テスト駆動開発のアプローチでUIコンポーネントを作成する
はじめに
今回は、ユーザー登録ページ(/users/sign_up)やログインページ(/users/sign_in)などへのリンクを置くナビゲーションバーを作成します。Deviseですぐに使える機能は「ログイン」や「ログアウト」などの機能ですが、色々なページからすぐにアクセスできるようにナビゲーションバーを作成しておくと便利です。
仕様の確認
先に完成形を確認しておきましょう。ユーザーがログインしているかどうかによって、表示するリンクを切り替えます。
ログインしていない場合
ヘッダーにユーザー登録、ログイン画面へのリンクが表示されます。 ユーザー登録画面へのリンクは /users/sign_up、ログイン画面へのリンクは /users/sign_in です。

ログインしている場合
ヘッダーにログアウトリンクが表示されます。(正確にはボタン要素ですが、便宜上リンクと呼びます。)逆に、ユーザー登録/ログインのリンクは表示されません。

イメージを掴んだところで、実装をしていきましょう。
RSpecでのログイン設定
上記の仕様の通り、今回は「ログインしているかどうか」によって表示されるリンクが変わります。テスト内でログインが必要な操作をする前に毎回ログインフォームからログインをするのは面倒です。
これを解決するために、Deviseのヘルパーメソッドを使えるようにします。この設定をしておくと sign_in user という形で1行記述するだけで、RSpecテスト内でログインした状態を実現できます。
spec/rails_helper.rbの最後の方で Devise::Test::IntegrationHelpers というモジュールをincludeしておきます。
  ...  config.include FactoryBot::Syntax::Methods  config.include Devise::Test::IntegrationHelpers, type: :system # 追加endこれでSystem Specの中で sign_in user という1行を書くだけで、user でログインした状態を実現することができます。実際の使い方は後ほど確認しますが、このようにDevise gemには便利なヘルパーメソッドがいくつか用意されているので、知れば知るほど活用の幅が広がります。
テスト追加
どの画面でも共通的に表示するものですので、どの画面のSystem Specでチェックしても問題ありませんが、例えばログイン画面はログインしているとトップページへリダイレクトされてしまうため、チェックには不向きです。
今回は既に作ってあるトップページ(Home#top)のSystem Specを修正しましょう。
require 'rails_helper'
RSpec.describe 'Home', type: :system do  before do    driven_by :rack_test  end
  describe 'トップページアクセスの検証' do    it 'Home#top という文字列が表示される' do      visit '/'      expect(page).to have_content('Home#top')    end  end
  ####### ここから追加 #######  describe 'ナビゲーションバーの検証' do    context 'ログインしていない場合' do      before { visit '/' }
      it 'ユーザー登録リンクを表示する' do        expect(page).to have_link('ユーザー登録', href: '/users/sign_up')      end
      it 'ログインリンクを表示する' do        expect(page).to have_link('ログイン', href: '/users/sign_in')      end
      it 'ログアウトリンクは表示しない' do        expect(page).not_to have_content('ログアウト')      end    end
    context 'ログインしている場合' do      before do        user = create(:user) # ログイン用のユーザーを作成        sign_in user # 作成したユーザーでログイン        visit '/'      end
      it 'ユーザー登録リンクは表示しない' do        expect(page).not_to have_link('ユーザー登録', href: '/users/sign_up')      end
      it 'ログインリンクは表示しない' do        expect(page).not_to have_link('ログイン', href: '/users/sign_in')      end
      it 'ログアウトリンクを表示する' do        expect(page).to have_content('ログアウト')      end
      it 'ログアウトリンクが機能する' do        click_button 'ログアウト'        # ログインしていない状態のリンク表示パターンになることを確認        expect(page).to have_link('ユーザー登録', href: '/users/sign_up')        expect(page).to have_link('ログイン', href: '/users/sign_in')        expect(page).not_to have_content('ログアウト')      end    end  endendこの時点では当然、テストが失敗することを確認します。まず理想となる仕様をテストとして定義し、その後に実装を進め、テストが成功するようにするのがテスト駆動開発の基本でしたね。
ただし、一部リンクが「表示されないこと」を確認しているテストもあるため、一部のテストは失敗しないことに注意してください。
$ bin/rspec spec/system/home_spec.rb
...
Finished in 11.44 seconds (files took 0.45402 seconds to load)8 examples, 4 failures
Failed examples:rspec ./spec/system/home_spec.rb:20 # Home ナビゲーションバーの検証 ログインしていない場合 ユーザー登録リンクを表示するrspec ./spec/system/home_spec.rb:24 # Home ナビゲーションバーの検証 ログインしていない場合 ログインリンクを表示するrspec ./spec/system/home_spec.rb:48 # Home ナビゲーションバーの検証 ログインしている場合 ログアウトリンクを表示するrspec ./spec/system/home_spec.rb:52 # Home ナビゲーションバーの検証 ログインしている場合 ログアウトリンクが機能する実装
では、上記のテストが通るように実装していきましょう。
部分テンプレート作成
app/views/shared というディレクトリを作り、_navbar.html.erb というファイルを作成します。このように、色々なページで共通して使うようなビューを「部分テンプレート」(またはパーシャル)と呼びます。
$ mkdir app/views/shared$ touch app/views/shared/_navbar.html.erbでは、ナビゲーションバーのHTMLを記述していきます。
<nav class="bg-gray-800 border-gray-200 px-2 sm:px-4 py-2.5">  <div class="container flex flex-wrap justify-between items-center mx-auto">    <%= link_to "TechLog", "/", class: "self-center text-white text-xl font-semibold whitespace-nowrap" %>    <div class="w-full md:block md:w-auto">      <ul class="flex flex-col mt-4 md:flex-row md:space-x-8 md:mt-0 md:text-sm md:font-medium">        <% if current_user %>          <li>            <%= button_to "ログアウト", destroy_user_session_path, class: "block py-2 pr-4 pl-3 text-gray-200 hover:text-white border-b border-gray-700 hover:bg-gray-700 md:hover:bg-transparent md:border-0 md:hover:text-blue-white md:p-0", method: :delete %>          </li>        <% else %>          <li>            <%= link_to "ユーザー登録", new_user_registration_path, class: "block py-2 pr-4 pl-3 text-gray-200 hover:text-white border-b border-gray-700 hover:bg-gray-700 md:hover:bg-transparent md:border-0 md:hover:text-blue-white md:p-0" %>          </li>          <li>            <%= link_to "ログイン", new_user_session_path, class: "block py-2 pr-4 pl-3 text-gray-200 hover:text-white border-b border-gray-700 hover:bg-gray-700 md:hover:bg-transparent md:border-0 md:hover:text-blue-white md:p-0" %>          </li>        <% end %>      </ul>    </div>  </div></nav>current_user は、Deviseで用意されているヘルパーメソッドです。ログインしていれば current_user にUserオブジェクトが入りますので、if文の条件分岐で利用しています。
ここで注意すべき点は、ログアウトリンクだけは link_to ではなく button_to を使用していることです。これは、デフォルトでは link_to に method: :delete をただ追加してもDELETEメソッドを実現できないためです。
参考:【Ruby】link_to の destroy アクションが機能しないときの対処法
では、今回作成した部分テンプレートを app/views/layouts/application.html.erb に追記します。ついでにbodyなど他要素も編集しつつ、以下のように<body>タグ内を書き換えてください。
  ...略  <body class="h-screen bg-blue-50">    <%= render 'shared/navbar' %>    <main class="container mx-auto mt-20 py-8 px-5 flex items-center justify-center">      <%= yield %>    </main>  </body></html>テスト実行
実装が完了しましたので、テストがすべて成功することを確認します。
$ bin/rspec spec/system/home_spec.rb
...
  ナビゲーションバーの検証    ログインしていない場合      ユーザー登録リンクを表示する      ログインリンクを表示する      ログアウトリンクは表示しない    ログインしている場合      ユーザー登録リンクは表示しない      ログインリンクは表示しない      ログアウトリンクを表示する      ログアウトリンクが機能する
Finished in 3.45 seconds (files took 0.41146 seconds to load)8 examples, 0 failures現時点ではまだユーザー登録画面がnicknameカラムに対応していませんが、RSpecではsign_inというヘルパーメソッドのおかげで、統合テストとして確認することができました。
変更をコミット
これでナビゲーションバーの実装は完了です。変更をコミットしておきます。
$ git add .$ git commit -m "ナビゲーションバーを追加"$ git pushまとめ
本章では、ナビゲーションバーを作成し、ユーザーのログイン状態に応じて表示を切り替える方法を学びました。部分テンプレートを使って共通のUIコンポーネントを作成し、レイアウトファイルに組み込む方法も理解できたと思います。
また、RSpecのシステムテストでDeviseのヘルパーメソッドを使用してログイン状態をシミュレートする方法も学びました。テスト駆動開発のアプローチで、まずはテストを書いてから実装を進めることで、仕様通りの機能が実現できていることを確認できました。
次章では、ユーザー登録画面をカスタマイズして、nicknameフィールドを追加していきます。
Basicプランでより詳しく学習
この先のコンテンツを読むにはBasicプラン以上が必要です。より詳細な解説、実践的なサンプルコード、演習問題にアクセスして学習を深めましょう。