学習ログ一覧機能作成
今回は、投稿した学習ログの一覧ページ作成していきましょう。
実装
ルーティング
前回追加した recources :posts
に index
アクションも追加してあげましょう。
config/routes.rb
Rails.application.routes.draw do
devise_for :users
root 'home#top'
resources :posts, only: [:new, :create, :show, :index] # 追加
end
これで、/posts
というパスへアクセスすると学習ログの一覧ページのアクションへ飛ぶようになります。
ページを表示するためには index アクションが必要となるので Controller にアクションと、対応する View を追加していきましょう。
Controller (#index)
PostsController に index
アクションを用意してあげます。
外部から呼び出すアクション (メソッド )なので、private の上に書くことに注意してください。
app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:show]
...
def index # 追加
@posts = Post.limit(10).order(created_at: :desc)
end
private
...
また、TechLog では投稿の閲覧についてはログインを必要としないため、
show アクションと同じくauthenticate_user!
から index アクションも対象外とします。
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:show, :index] # 修正
...
次は View を用意しましょう。
View
index アクションの view ファイルは用意されていないので、ファイルを作成してあげましょう。
$ touch app/views/posts/index.html.erb
中身は次のように編集してください。
app/views/posts/index.html.erb
<div class="space-y-6 w-3/4 max-w-lg">
<label class="block text-xl font-bold text-gray-700">学習ログ一覧</label>
<div class="items-center justify-center">
<% @posts.each do |post| %>
<div tabindex="0" aria-label="card 1" class="focus:outline-none mb-7 bg-white p-6 shadow rounded">
<div class="flex items-center border-b border-gray-200 pb-6">
<div class="flex items-start justify-between w-full">
<div class="pl-3">
<p class="focus:outline-none text-lg font-medium leading-5 text-gray-800"><%= link_to post.title, post_path(post) %></p>
<p class="focus:outline-none text-sm leading-normal pt-2 text-gray-500">by <%= post.user.nickname %></p>
</div>
</div>
</div>
<div class="px-2">
<p class="focus:outline-none text-sm leading-5 py-4 text-gray-600"><%= post.content %></p>
</div>
</div>
<% end %>
</div>
</div>
ナビゲーションバーにリンク追加
最後に、ナビゲーションバーに投稿一覧ページへのリンクを追加してあげます。
投稿一覧はログイン状態に関係なく閲覧できるため、ログイン状態を判定する if 文の外に書いてあげましょう。
app/views/shared/_navbar.html.erb
...
<ul class="flex flex-col mt-4 md:flex-row md:space-x-8 md:mt-0 md:text-sm md:font-medium">
<%# ここから追加 %>
<li>
<%= link_to "ログ一覧", posts_path, class: "block py-2 pr-4 pl-3 text-gray-400 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>
<%# 追加ここまで %>
<% if current_user %>
...
Controller (#new)
最後に、投稿した後のリダイレクト先を変えましょう。
現在は一時的にルートページへリダイレクトしていましたが、投稿一覧ページへリダイレクトするように変更します。
app/controllers/posts_controller.rb
def create
@post = Post.new(post_params)
@post.user_id = current_user.id # ログインユーザのIDを代入
if @post.save
flash[:notice] = '投稿しました'
redirect_to posts_path # 修正
System Spec もこれに合わせて修正しておきます。
spec/system/posts_spec.rb
...
describe 'ログ投稿機能の検証' do
...
context 'ログインしている場合' do
before do
sign_in @user
visit '/posts/new'
end
it 'ログインページへリダイレクトしない' do
expect(current_path).not_to eq('/users/sign_in')
end
context 'パラメータが正常な場合' do
it 'Postを作成できる' do
expect { subject }.to change(Post, :count).by(1)
expect(current_path).to eq('/posts') # 修正
動作確認
では、実際に画面で確認していきましょう。
ログイン時
開発用サーバを起動し、投稿画面から何らかの投稿をします。
その後、http://localhost:3000/posts
へアクセスすると投稿の一覧画面が開きます。
一覧ページへアクセスすると、このような見た目になっています。
<img width="542" alt="スクリーンショット 2022-08-06 10 05 09" src="https://user-images.githubusercontent.com/68495563/183227578-1062527b-0d2e-4728-9d99-5a1365cc6151.png">
ナビゲーションバーにもログ一覧のリンクが追加されていることを確認しておきます。
<img width="290" alt="image" src="https://user-images.githubusercontent.com/68495563/183227667-5465081d-f65b-4f7b-b60b-e689f5869bdd.png">
テスト
さて、実装は完了したので上記の動作を保証するテストを書いていきましょう。
Request Spec
まずは、新しく作った一覧ページへのアクセスを確認する Request Spec を追加していきます。
spec/requests/posts_spec.rb
...
describe 'GET /posts' do
context 'ログインしていない場合' do
it 'HTTPステータス200を返す' do
get '/posts'
expect(response).to have_http_status '200'
end
end
context 'ログインしている場合' do
it 'HTTPステータス200を返す' do
sign_in @user
get '/posts'
expect(response).to have_http_status '200'
end
end
end
...
Request Spec の全文は以下のようになります。
<details>
<summary>spec/requests/posts_spec.rb</summary>
require 'rails_helper'
RSpec.describe 'Posts', type: :request do
before do
@user = create(:user) # 各テストで使用できるユーザーを作成
@post = create(:post) # 閲覧用の Post を作成
end
describe 'GET /posts/new' do
context 'ログインしていない場合' do
it 'HTTPステータス302を返す' do
get '/posts/new'
expect(response).to have_http_status(302)
end
it 'ログインページにリダイレクトされる' do
get '/posts/new'
expect(response).to redirect_to '/users/sign_in'
end
end
context 'ログインしている場合' do
before { sign_in @user }
it 'HTTPステータス200を返す' do
get '/posts/new'
expect(response).to have_http_status(200)
end
it 'ログインページにリダイレクトされない' do
get '/posts/new'
expect(response).not_to redirect_to '/users/sign_in'
end
end
end
describe 'GET /posts/:id' do
context 'ログインしていない場合' do
it 'HTTPステータス200を返す' do
get "/posts/#{@post.id}"
expect(response).to have_http_status '200'
end
end
context 'ログインしている場合' do
it 'HTTPステータス200を返す' do
sign_in @user
get "/posts/#{@post.id}"
expect(response).to have_http_status '200'
end
end
end
describe 'GET /posts' do
context 'ログインしていない場合' do
it 'HTTPステータス200を返す' do
get '/posts'
expect(response).to have_http_status '200'
end
end
context 'ログインしている場合' do
it 'HTTPステータス200を返す' do
sign_in @user
get '/posts'
expect(response).to have_http_status '200'
end
end
end
end
</details>
これで Request Spec は完了です。
次は System Spec を作成してあげましょう。
System Spec
Factory Bot を用いて事前に Post を2つ作成してから、一覧ページの表示内容を確認するテストにします。
System Spec に以下のテストを追記します。
spec/system/posts_spec.rb
require 'rails_helper'
describe 'Post', type: :system do
before do
driven_by :selenium_chrome_headless # ヘッドレスモードで実行
@user = create(:user) # ログイン用ユーザー作成
@post = create(:post, title: 'RSpec学習完了', content: 'System Specを作成した', user_id: @user.id)
@post2 = create(:post, title: 'RSpec学習完了 2', content: 'System Specを作成した 2', user_id: @user.id) # 追加
end
...
####### ここから追加 #######
describe 'ログ一覧機能の検証' do
before { visit '/posts' }
it '1件目のPostの詳細が表示される' do
expect(page).to have_content('RSpec学習完了')
expect(page).to have_content('System Specを作成した')
expect(page).to have_content(@user.nickname)
end
it '2件目のPostの詳細が表示される' do
expect(page).to have_content('RSpec学習完了 2')
expect(page).to have_content('System Specを作成した 2')
expect(page).to have_content(@user.nickname)
end
it '投稿タイトルをクリックすると詳細ページへ遷移する' do
click_link 'RSpec学習完了'
expect(current_path).to eq("/posts/#{@post.id}")
end
end
end
この時点での System Spec の全文は以下の通りです。
<details>
<summary>spec/system/posts_spec.rb</summary>
require 'rails_helper'
describe 'Post', type: :system do
before do
driven_by :selenium_chrome_headless # ヘッドレスモードで実行
@user = create(:user) # ログイン用ユーザー作成
@post = create(:post, title: 'RSpec学習完了', content: 'System Specを作成した', user_id: @user.id)
@post2 = create(:post, title: 'RSpec学習完了 2', content: 'System Specを作成した 2', user_id: @user.id)
end
# 投稿フォーム
let(:title) { 'テストタイトル' }
let(:content) { 'テスト本文' }
describe 'ログ投稿機能の検証' do
# ログ投稿を行う一連の操作を subject にまとめる
subject do
fill_in 'post_title', with: title
fill_in 'post_content', with: content
click_button 'ログを記録'
end
context 'ログインしていない場合' do
before { visit '/posts/new' }
it 'ログインページへリダイレクトする' do
expect(current_path).to eq('/users/sign_in')
expect(page).to have_content('ログインしてください。')
end
end
context 'ログインしている場合' do
before do
sign_in @user
visit '/posts/new'
end
it 'ログインページへリダイレクトしない' do
expect(current_path).not_to eq('/users/sign_in')
end
context 'パラメータが正常な場合' do
it 'Postを作成できる' do
expect { subject }.to change(Post, :count).by(1)
expect(current_path).to eq('/posts')
expect(page).to have_content('投稿しました')
end
end
context 'パラメータが異常な場合' do
let(:title) { nil }
it 'Postを作成できない' do
expect { subject }.not_to change(Post, :count)
expect(page).to have_content('投稿に失敗しました')
end
it '入力していた内容は維持される' do
subject
expect(page).to have_field('post_content', with: content)
end
end
end
end
describe 'ログ詳細機能の検証' do
before { visit "/posts/#{@post.id}" }
it 'Postの詳細が表示される' do
expect(page).to have_content('RSpec学習完了')
expect(page).to have_content('System Specを作成した')
expect(page).to have_content(@user.nickname)
end
end
describe 'ログ一覧機能の検証' do
before { visit '/posts' }
it '1件目のPostの詳細が表示される' do
expect(page).to have_content('RSpec学習完了')
expect(page).to have_content('System Specを作成した')
expect(page).to have_content(@user.nickname)
end
it '2件目のPostの詳細が表示される' do
expect(page).to have_content('RSpec学習完了 2')
expect(page).to have_content('System Specを作成した 2')
expect(page).to have_content(@user.nickname)
end
it '投稿タイトルをクリックすると詳細ページへ遷移する' do
click_link 'RSpec学習完了'
expect(current_path).to eq("/posts/#{@post.id}")
end
end
end
</details>
テスト実行
最後に、すべてのテストに通ることを確認しておきます。
$ bin/rspec
...
Finished in 12.21 seconds (files took 0.41113 seconds to load)
58 examples, 0 failures
変更をコミット
ここまでの変更をコミットしておきます。
$ git add .
$ git commit -m "学習ログ一覧機能を作成"
$ git push