Ruby on Rails 비동기 통신 (ajax)에 대한 되돌아보기

커리큘럼 학습·포트폴리오 작성중, 이 기능을 구현하는데 고전했으므로, 메모.
비동기 통신으로 하고 싶었던 것은 이하의 2점.

① 특정 상품을 즐겨찾기 등록 또는 삭제


② 즐겨찾기 목록에서 삭제


전제 조건



테이블



schema.rb

  create_table "users", force: :cascade do |t|
()
  end
 create_table "products", force: :cascade do |t|
()
  end

  create_table "favorites", force: :cascade do |t|
    t.integer "user_id", null: false
    t.integer "product_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

모델 (연결)



favorite.rb
belongs_to :user
belongs_to :product


user.rb
has_many :favorites, dependent: :destroy

product.rb
has_many :favorites, dependent: :destroy

def favorited_by?(user)
  Favorite.where(user_id: user.id).exists?
end

루트



rutes.rb
resources :products do
 resource :favorites, only: [:create, :destroy]
end

실장①(상품의 즐겨찾기 추가・삭제)



products 컨트롤러는 다음과 같습니다

products_controller.rb
def show
  @product = Product.find(params[:id])
end

다음으로 즐겨찾기 버튼을 부분화. (사용자 디렉토리에 작성)

views/users/products/_favorite_button.html.erb
<% if product.favorites.where(user_id: current_user.id).exists? %>
  <%= link_to "お気に入りから削除",  users_product_favorites_path(product_id: product.id), method: :delete, remote: true %>
<% else %>
  <%= link_to "お気に入りに追加", users_product_favorites_path(product_id: product.id), method: :post, remote: true %>
<% end %>

여기서 remote: true를 붙이면 비동기 통신이 가능합니다.

products/show.html.erb에서 호출

views/users/products/show.html.erb
<div id="favorites_buttons_<%= @product.id %>">
  <%= render 'users/products/favorite_button', product: @product %>
</div>

다음은 favorites 컨트롤러.

fovorites_controller.rb
  def create
    @product = Product.find(params[:product_id])
    favorite = current_user.favorites.new(product_id: @product.id)
    favorite.save
  end

  def destroy
    @product = Product.find(params[:product_id])
    @favorites = current_user.favorites
    favorite = current_user.favorites.find_by(product_id: @product.id)
    favorite.destroy
  end

여기서 리디렉션 대상을 지정하지 않으면 JS의 처리를 찾으러 간다.
마지막으로 작성·삭제했을 경우의 JS 처리를 작성한다.

views/users/favorites/create.js.erb
$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");

views/users/favorites/destroy.js.erb
$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");

show.html.erb 내의 id로 설정한 범위만이 처리 후 다시 쓰여지게 된다.

구현②(즐겨찾기 목록에서 삭제)



fovorites의 컨트롤러는 아래와 같이 작성

favorites_controller.rb
def index
  @favorites = current_user.favorites
end

비동기 통신하고 싶은 범위를 부분화.

view/users/favorites/_form.html.erb
<% if favorites.present? %>
  <table class="table">
    <thead>
      <tr>
        <th colspan="3">商品</th>
      </tr>
    </thead>
    <tbody>
      <% favorites.each do |f| %>
        <tr>
          <td>
            <%= attachment_image_tag f.product, :image, :fill, 80, 80, format: 'jpeg', fallback: "no_image.jpg", size: '80x80' %>
          </td>
          <td><%= link_to f.product.name, users_product_path(f.product.id) %></td>
          <td><%= link_to "お気に入りから削除",  users_product_favorites_path(product_id: f.product.id), method: :delete, remote: true %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
<% else %>
  <h3>
    お気に入りリストがありません。<br>
    商品ページから追加してみましょう
  </h3>
<% end %>

fovorites/index.html.erb에서 호출
<div class="row">
  <div class="col-sm-6 offset-3" id="favorites_index">
    <%= render 'users/favorites/form', favorites: @favorites %>
  </div>
</div>

JS의 처리에 이하의 1행을 추가

views/users/favorites/destroy.js.erb
$("#favorites_index").html("<%= j(render 'users/favorites/form', favorites: @favorites) %>");

이것으로 즐겨찾기 목록에도 비동기 기능을 구현할 수 있었다.

실장한 후의 반성점·감상


  • JS에 기재하고 있는 인스턴스 변수는 각각 create·destroy 메소드로부터 확인하고 있기 때문에, 그 쪽에서도 정의해 주지 않으면 안 된다는 것을 몸에 익혔다. (생각해 보면 당연했지만)
  • 실장①에 대해서는 구그하면 잘 나오지만,②는 보이지 않으므로 자력으로
    풀어 감동했다. 단지 흐름을 이해하고 있으면 헤매는 곳에서도 없었다고 느꼈다.
  • 좋은 웹페이지 즐겨찾기