学習ログ詳細機能作成
今回は、投稿した学習ログの詳細ページ作成していきましょう。
実装
ルーティング
前回追加した recources :posts
に show
アクションも追加してあげましょう。
config/routes.rb
Rails.application.routes.draw do
devise_for :users
root 'home#top'
resources :posts, only: [:new, :create, :show] # 追加
end
これで、/posts/:id
というパスへアクセスすると学習ログの詳細ページのアクションへ飛ぶようになります。
ページを表示するためには show アクションが必要となるので Controller にアクションと、対応する View を追加していきましょう。
Controller (#show)
PostsController に show
アクションを用意してあげます。
外部から呼び出すアクション (メソッド )なので、private の上に書くことに注意してください。
app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :authenticate_user!
...
def show # 追加
@post = Post.find_by(id: params[:id])
end
private
def post_params
params.require(:post).permit(:title, :content)
end
end
/posts/:id
の :id
部分をパラメータとして受け取り、該当する Post
を取得しています。
また、TechLog では投稿の閲覧についてはログインを必要としないため、authenticate_user!
から show アクションは対象外とします。
class PostsController < ApplicationController
before_action :authenticate_user!, except: [:show] # 修正
...
次は View を用意しましょう。
View
show アクションの view ファイルは用意されていないので、ファイルを作成してあげましょう。
$ touch app/views/posts/show.html.erb
中身は次のように編集してください。
app/views/posts/show.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">
<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>
</div>
</div>
動作確認
では、実際に画面で確認していきましょう。
ログイン時
開発用サーバを起動し、投稿画面から何らかの投稿をします。
その後、http://localhost:3000/posts/xx
へアクセスすると投稿の詳細画面が開きます。
(xx
の部分は投稿回数に応じて数字を置き換えてください。わからなければ1
を当てはめてください)
詳細ページへアクセスすると、このような見た目になっています。
ログアウト時
先ほどの投稿詳細画面の URL を一旦コピーしてからログアウトします。
その後、もう一度同じ URL にアクセスしましょう。
投稿画面とは異なり、ログインページへリダイレクトされずにそのまま詳細画面が表示されます。
ここまで画面で確認できれば OK です。
ナビゲーションバー(ログイン時)
ログイン時は「ログ投稿」というリンクが表示されていることを確認します。
ナビゲーションバー(ログアウト時)
ログアウト時は「ログ投稿」というリンクが表示されていないことを確認します。
テスト
さて、実装は完了したので上記の動作を保証するテストを書いていきましょう。
Request Spec
まずは、新しく作った詳細ページへのアクセスを確認する Request Spec を追加していきます。
今回は事前に Post が一つ以上は存在している必要があるため、Post の FactoryBot を使っていることに注意しましょう。
spec/requests/posts_spec.rb
require 'rails_helper'
RSpec.describe 'Posts', type: :request do
before do # beforeブロックを修正
@user = create(:user)
@post = create(:post) # 追加
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
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
end
</details>
ログイン状態によらずリダイレクトされずアクセスできることを含め、アクセス性を確認するテストを作成しました。
テストを実行してみてください。
すべて成功すれば OK です。
$ bin/rspec spec/requests/posts_spec.rb
DEBUGGER: Attaching after process 8205 fork to child process 10953
Running via Spring preloader in process 10953
Posts
GET /posts/new
ログインしていない場合
HTTPステータス302を返す
ログインページにリダイレクトされる
ログインしている場合
HTTPステータス200を返す
ログインページにリダイレクトされない
GET /posts/:id
ログインしていない場合
HTTPステータス200を返す
ログインしている場合
HTTPステータス200を返す
Finished in 0.51404 seconds (files took 0.70362 seconds to load)
6 examples, 0 failures
これで Request Spec は完了です。
次は System Spec を作成してあげましょう。
System Spec
こちらも Factory Bot を用いて事前に Post を1つ作成してから、詳細ページの表示内容を確認するテストにします。
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) # 追加
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
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)
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('/')
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
end
</details>
テスト実行
最後に、すべてのテストに通ることを確認しておきます。
$ bin/rspec
...
Finished in 12 seconds (files took 0.69987 seconds to load)
51 examples, 0 failures
変更をコミット
ここまでの変更をコミットしておきます。
$ git add .
$ git commit -m "学習ログ詳細機能を作成"
$ git push