Chapter 24

4-5. 学習ログ削除機能作成

Masuyama
Masuyama
2022.10.15に更新

学習ログ削除機能作成

今回は、学習ログ関連機能の締めくくりとして削除機能を実装します。

実装

ルーティング

PostsController の各アクションへのルーティングについては resources で指定していました。
許可するアクションとして destroyも追加してあげましょう。

config/routes.rb

resources :posts, only: [ :index, :new, :create, :show, :destroy] # 修正

Controller (#destroy)

次に Controller にアクションを追加します。

app/controllers/posts_controller.rb

  def destroy
    @post = Post.find_by(id: params[:id])
    if @post.user == current_user
      @post.destroy
      flash[:notice] = '投稿が削除されました'
    end
    redirect_to posts_path
  end

毎度おなじみですが、private よりも上に追加することに注意しましょう。

View

最後に、view ファイルを編集します。

削除機能については個別の view は用意せず、投稿の詳細ページの中に削除ボタンを設置します。
ただし、他の人の投稿は削除できないよう、Post のユーザーとログインユーザーが一致する場合にのみ削除ボタンを表示させます。

以上の要件を満たした view ファイルが以下の通りです。
部分的に追加しています。

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>
            <%# ここから追加 %>
            <% if user_signed_in? %> 
              <% if @post.user_id == current_user.id %>
                <%= button_to "削除", post_path(@post), method: :delete, class: "text-sm bg-transparent hover:bg-blue-500 text-blue-700 hover:text-white py-1 px-3 border border-blue-500 hover:border-transparent rounded" %>
              <% end %>
            <% end %>
          </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>

devise のヘルパーメソッド user_signed_in? を使っています。
これはログインしていれば trueを返すヘルパーメソッドです。

これがないと、ログインしていない時には current_user が nil になるため、current_user.id のところで NoMethodError を吐いてしまうので注意してください。

<% if user_signed_in? %> がない場合のエラー

スクリーンショット 2022-08-01 9 32 42

動作確認

それでは動作確認していきましょう。

削除機能は、投稿したユーザーかどうかで挙動が変わるため、両方のパターンで動作確認しておきます。

投稿したユーザーの場合

まずは投稿したユーザー本人の場合です。
適当なユーザーでログインし、1件学習ログを投稿した後にその投稿の詳細画面を開きます。

http://localhost:3000/posts/:id
(:idには投稿した Post の ID を当てはめてください)

image

また、削除ボタンを押すとフラッシュメッセージ表示されると共に投稿が削除されつつ、
学習ログの一覧からも消えていることを確認してください。

image

投稿したユーザー以外の場合

次は投稿したユーザー以外の表示です。
別なユーザーを作ってログイン、またはログアウトし、学習ログ一覧から任意の投稿の詳細画面にアクセスしてください。
削除ボタンが表示されていなければ OK です。

image

テスト

それでは、削除機能に関するテストを追加しましょう。
先ほど動作確認した内容を System Spec に追加します。

  ...

  describe 'ログ削除機能の検証' do
    context '投稿したユーザーでログインしている場合' do
      before do
        sign_in @user
        visit "/posts/#{@post.id}"
      end

      it '削除ボタンを表示する' do
        expect(page).to have_button('削除')
      end

      it '削除ボタンをクリックすると削除できる' do
        expect do
          click_button '削除'
        end.to change(Post, :count).by(-1) # 削除ボタンをクリックするとPostが1つ減る

        # リダイレクト後の画面確認
        expect(current_path).to eq('/posts')
        expect(page).to have_content('投稿が削除されました') # フラッシュメッセージを表示
        expect(page).not_to have_link("/posts/#{@post.id}") # 削除した投稿(の詳細ページへのリンク)が存在しない
      end
    end

    context '投稿したユーザーでログインしていない場合' do
      it '削除ボタンを表示しない' do
        visit "/posts/#{@post.id}"
        expect(page).not_to have_button('削除')
      end

      it '直接リクエストを投げても削除されない' do
        visit "/posts/#{@post.id}"
        
        expect do
          delete post_path(@post) # 投稿データを削除するリクエストを送る
        end.not_to change(Post, :count)
      end
    end
  end
end

最後にすべてのテストを実行し、通ることを確認します。

$ bin/rspec
...
Finished in 13.55 seconds (files took 0.41183 seconds to load)
62 examples, 0 failures

変更をコミット

ここまでの変更をコミットしておきましょう。

$ git add .
$ git commit -m "学習ログ削除機能を作成" 
$ git push

宿題

user_signed_in?

今回、詳細ページで表示の切り替えをするために devise のヘルパーメソッドである user_signed_in? を使いました。

Controller, View のどちらでも使用できる便利なメソッドですので、改めて使い方はおさえておきましょう。