【Rails】Ajaxとパーシャルで非同期処理のいいねボタンを実装する

プログラミング初心者の勉強ブログ #82

今回は、非同期処理で画面遷移のない「いいねボタン」を実装する方法をまとめます。ボタンを構成するHTMLをパーシャルで分割し、クリック時にajaxでそのパーシャルを呼び出し、画面のボタン部分のみを切り替えます。ブログアプリにつける「いいねボタン」をよりスマートにするためのやり方を書いていきます。

 

目次

[toc]

 

実装コードと解説

いいね!ボタン

今回の実装は、Railsブログアプリの「いいね機能(お気に入り機能)」の実装にあたり、非同期処理でDBに保存後、ボタン表示を画面遷移なく切り替えます。画面表示にはパーシャルを用いております。

 

流れとしては、

  1. 「いいね!」ボタンがクリックされる
  2. Ajax通信でfavoritesコントローラーが叩かれる
  3. DB処理の実行後、HTMLの書き換えが行われる
  4. 書き換えられたHTMLによってパーシャルがレンダリングされる
  5. 「いいね!をはずす」ボタンが表示される

このような形です。単なる画面表示の切り替えであればJS使うだけで大丈夫ですが、DBへのクエリ発行やパーシャルを使っての切り替えを行うとなると、一旦コントローラーを経由させることになります。

以下、コードと解説です。

 

Viewのコード

blogs/show.html.erb(抜粋)

// idを付与したdivを作成
<div id="favorite_btn">
 <%= render 'favorite_form', blog: @blog, favorite: @favorite %>
</div>

「favorite_btn」というidを付与したdivを作成します。このidをセレクタに取ったjQuery文を、後の「favorites/index.js.erb」内で書き込むことで、いいねボタンが切り替わります。

ボタンクリックの度に切り替えますが、最初の画面で表示させるために「favorite_btn」div内にボタンを表示させる記述をします。「render ‘favorite_form’」で「blogs/_favorite_form.html.erb」をレンダリング、「blog: @blog, favorite: @favorite」でレンダリング先にパラメータで変数@blogと@favoriteを送る記述になります。

 

blogs/_favorite_form.html.erb(抜粋)

<% if @blog.favorite?(@blog, current_user) %>
 <li class="like_off">
  <%= link_to 'いいね!をはずす', favorite_path(id: @blog.id), method: :delete, remote: true, class: "like_off_btn" unless @blog.user_id == current_user.id %>
 </li>
<% else %>
 <li class="like_on">
  <%= link_to 'いいね!', favorites_path(blog_id: @blog.id), method: :post, remote: true, class: "like_on_btn" unless @blog.user_id == current_user.id %>
 </li>
<% end %>

レンダリングされる「いいねボタン」を分割したパーシャルです。お気に入りが既にされているか否かで条件分岐を行います。

 

「@blog.favorite?(@blog, current_user)」はblog.rbで作成したメソッドです。「Modelのコード」の所にも載せてますが、

def favorite?(blog, user)
 blog.favorites.find_by(user_id: user.id)
end

このようなメソッドです。引数を「(@blog, current_user)」とすることで、

「@blog.favorites.find_by(user_id: current_user.id)」となり、「@blogにいいねしているユーザーの中に、カレントユーザーがいたらtrueを返す」メソッドとして働きます。

 

また、「remote: true」で非同期処理を支持し、「favorite_path(id: @blog.id), method: 〇〇」によってfavoritesコントローラーのcreateアクションやdestroyアクションを叩いてくれます。

※「unless @blog.user_id == current_user.id」で、そのブログを書いた本人のページにはボタンが表示されないようにしてます。

※current_userはdeviseのヘルパーメソッドを使用してます。

 

favorites/index.js.erb

$("#favorite_btn").html("<%= escape_javascript(render partial: 'blogs/favorite_form', locals: { blog: @blog }) %>")

id「favorite_btn」をセレクタに取り、htmlでdivの中身を書き換える処理です。拡張子「js.erb」によって、同ファイル内でRubyとJSをどちらも書き込むことができます。(js.erbはあまりオススメされませんでしたが、ネット調べても沢山出てくるので、少し使う分には良いのかなと思います。)

「escape_javascript」はエスケープ処理を行うRailsのメソッドです。基本的に「スクリプト内に動的生成の文字列などを出力してはならない」らしく、エスケープ処理はそこらへんを上手いことしてくれるものらしいです。

このJSによって、「favorite_btn」divの中身を<%= render ‘favorite_form’, blog: @blog %>にもう一度書き換え直し、再度「_favorite_form.html.erb」パーシャルの条件分岐に入り、そのタイミングではfavoritesテーブルのデータが切り替わっているので、表示されるボタンが変わる、という仕組みです。

 

Controllerのコード

controllers/favorites_controller.rb

class FavoritesController < ApplicationController
 def create
  @favorite = current_user.favorites.create(blog_id: params[:blog_id])
  @blog = Blog.find(params[:blog_id])
  render 'index.js.erb'
 end

 def destroy
  @favorite = current_user.favorites.find_by(blog_id: params[:id].to_i).destroy
  @blog = Blog.find(params[:id])
  render 'index.js.erb'
 end
end

ボタンクリック時に叩かれるコントローラーです。いいね!クリックでcreate、いいね!をはずすクリックでdestroyアクションにつながり、favoriteテーブルを更新した後、@blogという変数を連れて「index.js.erb」をレンダリングします。パーシャル内で使うことになる変数はここで生成しておき、Viewに渡します。

 

createアクションの時のparams→「blog_id」

destroyアクションの時のparams→「id」

 

のようにパラメーターに違いが発生しているのは、「_favorite_form.html.erb」 で

<% if blog.favorite?(blog, current_user) %>
 <li class="like_off">
  <%= link_to 'いいね!をはずす', favorite_path(id: blog.id), method: :delete, remote: true, class: "like_off_btn" unless blog.user_id == current_user.id %>
 </li>
<% else %>
 <li class="like_on">
  <%= link_to 'いいね!', favorites_path(blog_id: blog.id), method: :post, remote: true, class: "like_on_btn" unless blog.user_id == current_user.id %>
 </li>
<% end %>

destroyのときは「favorite_path(id: blog.id)」、createのときは「favorites_path(blog_id: blog.id)」と、Ajax通信時に渡しているパラメータが違うためです。同じでも大丈夫ですが、このように少し変えてみると勉強になります。

 

Modelのコード

models/blog.rb(抜粋)

class Blog < ApplicationRecord
 has_many :favorites, dependent: :destroy
 has_many :favorite_users, through: :favorites, source: :user

 def favorite?(blog, user)
  blog.favorites.find_by(user_id: user.id)
 end
end

アソシエーションの記述と、パーシャルの条件分岐で使用した「favorite?」メソッドの定義をしております。

 

models/favorite.rb

class Favorite < ApplicationRecord
 belongs_to :user
 belongs_to :blog
end

※アソシエーションのみ

 

models/user.rb(抜粋)

class User < ApplicationRecord
 has_many :favorites, dependent: :destroy
 has_many :favorite_blogs, through: :favorites, source: :blog
end

※アソシエーションのみ

 

「favorites」テーブル(schemaファイル)

schema.rb(抜粋)

create_table "favorites", force: :cascade do |t|
 t.bigint "user_id"
 t.bigint "blog_id"
 t.datetime "created_at", null: false
 t.datetime "updated_at", null: false
 t.index ["user_id", "blog_id"], name: "index_favorites_on_user_id_and_blog_id", unique: true
end

favoritesテーブルのカラムです。blogとuserどちらもアソシエーションしております。

 

まとめ

非同期処理が何となくでも使えるようになると、アプリ作成の幅が広がります。RailsのActive Jobで非同期処理を練習してみたことで、非同期処理の漠然としたイメージが少し明瞭になった感覚があり、今回のAjaxも前やったときよりは理解が進みます。言ってもまだいまいちわからない所もいくつか残ってますが、次やったときにすんなり入ってくることを信じてとりあえず寝かしておきます。

以上ありがとうございました。

 

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA