【Rails】非同期通信化※復習用



## 1. **いいね機能の非同期通信化の手順**

### ステップ1: **コントローラの設定**
`FavoritesController`に非同期通信をサポートするための`create`と`destroy`アクションを設定します。

```ruby
class FavoritesController < ApplicationController
  before_action :authenticate_user!

  def create
    @book = Book.find(params[:book_id])
    current_user.favorites.create(book_id: @book.id)
    
    respond_to do |format|
      format.js  # create.js.erbを呼び出す
    end 
  end

  def destroy
    @book = Book.find(params[:book_id])
    current_user.favorites.find_by(book_id: @book.id).destroy
    
    respond_to do |format|
      format.js  # destroy.js.erbを呼び出す
    end 
  end
end
```

### ステップ2: **`create.js.erb`と`destroy.js.erb`を作成**
これらのファイルを使用して、ビューの更新を行います。

- **`create.js.erb`**
  ```javascript
  // いいねが押された場合、ボタンの表示を更新
  $(".favorite-btn-<%= @book.id %>").html("<%= j(render partial: 'favorites/favorite_btn', locals: { book: @book }) %>");
  ```

- **`destroy.js.erb`**
  ```javascript
  // いいね解除された場合、ボタンの表示を更新
  $(".favorite-btn-<%= @book.id %>").html("<%= j(render partial: 'favorites/favorite_btn', locals: { book: @book }) %>");
  ```

### ステップ3: **部分テンプレートの作成**
いいねボタンを部分テンプレート化します。

- **`_favorite_btn.html.erb`**
  ```erb
  <% if current_user.favorited?(@book) %>
    <%= button_to book_favorite_path(@book), method: :delete, remote: true do %>
      <i class="fa-solid fa-heart" style="color: #ff0a0a;"></i> <%= @book.favorites.count %>
    <% end %>
  <% else %>
    <%= button_to book_favorite_path(@book), method: :post, remote: true do %>
      <i class="fa-solid fa-heart" style="color: #74C0FC;"></i> <%= @book.favorites.count %>
    <% end %>
  <% end %>
  ```

### ステップ4: **ビューの更新**
`books/index.html.erb`や`books/show.html.erb`に部分テンプレートを使用していいねボタンを表示します。

```erb
<td class="favorite-btn-<%= book.id %>">
  <%= render "favorites/favorite_btn", book: book %>
</td>
```

---

## 2. **コメント機能の非同期通信化の手順**

### ステップ1: **コントローラの設定**
`BookCommentsController`に非同期通信をサポートする`create`と`destroy`アクションを設定します。

```ruby
class BookCommentsController < ApplicationController
  before_action :authenticate_user!
  before_action :ensure_correct_user, only: [:destroy]

  def create
    @book = Book.find(params[:book_id])
    @book_comment = current_user.book_comments.new(book_comment_params)
    @book_comment.book = @book
    if @book_comment.save
      respond_to do |format|
        format.js  # create.js.erbを呼び出す
      end 
    else
      render "books/show"
    end
  end

  def destroy
    @book_comment = BookComment.find(params[:id])
    @book_comment.destroy
    respond_to do |format|
      format.js  # destroy.js.erbを呼び出す
    end 
  end

  private

  def book_comment_params
    params.require(:book_comment).permit(:comment)
  end
  
  def ensure_correct_user
    @book_comment = BookComment.find(params[:id])
    unless @book_comment.user == current_user
      redirect_to books_path
    end
  end
end
```

### ステップ2: **`create.js.erb`と`destroy.js.erb`の作成**

- **`create.js.erb`**
  ```javascript
  // コメントリストの最後に新しいコメントを追加
  $("#comment-list").append("<%= j(render partial: 'book_comments/comment', locals: { comment: @book_comment }) %>");

  // フォームをリセット
  $("#new_book_comment")[0].reset();
  ```

- **`destroy.js.erb`**
  ```javascript
  // 削除されたコメントをDOMから削除
  $("#comment-<%= @book_comment.id %>").remove();
  ```

### ステップ3: **部分テンプレートの作成**
コメントを表示する部分を部分テンプレート化します。

- **`_comment.html.erb`**
  ```erb
  <tr id="comment-<%= comment.id %>">
    <td>
      <%= link_to user_path(comment.user) do %>
        <%= image_tag comment.user.get_profile_image, size: "50x50" %><br>
        <%= comment.user.name %>
      <% end %>
    </td>
    <td>
      <%= comment.comment %>
    </td>
    <td>
      <% if comment.user == current_user %>
        <%= link_to "Destroy", book_book_comment_path(comment.book, comment), method: :delete, remote: true %>
      <% end %>
    </td>
  </tr>
  ```

### ステップ4: **ビューの更新**
コメントリストとコメントフォームを含むビューを更新します。

- **`books/show.html.erb`**
  ```erb
  <table class="table" id="comment-list">
    <%= render partial: "book_comments/comment", collection: @book.book_comments %>
  </table>

  <div>
    <%= form_with model: [@book, @book_comment], local: false, id: "new_book_comment" do |f| %>
      <%= f.text_area :comment, placeholder: "コメントをここに", style: "width: 100%" %>
      <%= f.submit "送信" %>
    <% end %>
  </div>
  ```

---

## 注意ポイント

### 1. **`remote: true`の使用**
- 非同期通信を有効にするために、`button_to`や`form_with`で必ず`remote: true`を指定します。
  
### 2. **`create.js.erb`と`destroy.js.erb`の役割**
- 非同期通信で画面を部分的に更新するために、`create.js.erb`や`destroy.js.erb`を用意し、JavaScriptでDOM操作を行います。
- サーバー側でのデータ変更後に、ビューの一部だけを動的に更新します。

### 3. **エラーログとJavaScriptの確認**
- Railsのログで`Processing as JS`が表示されているか確認し、非同期通信が成功しているか確認します。
- ブラウザのデベロッパーツール（Consoleタブ）でJavaScriptエラーが発生していないかチェックします。

### 4. **JavaScriptセレクタ**
- JavaScriptで特定の要素を操作する際、正しいCSSセレクタを使っているか確認します。例えば、`$(".favorite-btn-<%= book.id %>")`や`$("#comment-list")`のようなIDやクラスが正しく設定されているかを確認しましょう。

---

このように、いいね機能とコメント機能の非同期通信化を行うことで、ページのリロードなしにユーザーの操作に対して即時反応するアプリケーションを作ることができます。