はじめに
今回は、ポートフォリオにいいね機能を実装してみて色々と苦労したので備忘録も兼ねて記事にしていきたいと思います。
「Rails いいね 実装」などでググると以下の記事を始めとして色々な方が解説してくれている記事を見つけることができます。
Railsチュートリアルライクな実装方法はこれらを見れば理解できたので、これらの記事と少し方法を変えてみたいなというモチベーションから.js.erb
を用いない方法でいいね機能を実装してみました。
前提
- スパイスカレー屋の検索・共有アプリを作成しており、UserがShopにいいねできるようにしたいという状況を想定しています。
- 既にUserリソースとShopリソースは作成されている状態から始めます。
- 認証系にはDeviseを用いており、
current_user
メソッドやuser_signed_in?
メソッド等が使用できる状態であるとします。 - ビューは
.erb
ではなく、.slim
を使って書きます。 - 本記事ではいいねボタンはBootstrap4で簡単にスタイリングします。
- 最終的には以下のように動くことを狙います。
開発環境
- Ruby 2.5.1
- Rails 5.2.2
いいね実装の具体的な流れ
必要なGemのインストール
jQueryが使えるようにあらかじめ'jquery-rails'
を入れておきます。
gem 'jquery-rails'
そしてapplication.js
を以下のようにしておきます。
(順番に気を付けないとコンソールでエラーが消えてくれないので注意です。)
・・・
//= require jquery3
//= require rails-ujs
//= require popper
//= require turbolinks
//= require bootstrap-sprockets
//= require_tree .
Likeモデルの作成・編集
まずLikeモデルを作成していきます。
$ rails g model Like user:references shop:references
モデル生成時にデータ型をreferences
としておくことで、以下のように何もしなくてもLikeモデルがUserとShopと関連付いていると認識してくれるようになるので便利です。
class Like < ApplicationRecord
belongs_to :user
belongs_to :shop
end
いいね機能において、UserとShopの関係は1:1なので、マイグレーションファイルにadd_index :likes, [:user_id, :shop_id], unique: true
を追記しておきます。
その後、$ rails db:migrate
を実行します。
class CreateLikes < ActiveRecord::Migration[5.2]
def change
create_table :likes do |t|
t.references :shop, foreign_key: true
t.references :user, foreign_key: true
t.timestamps
end
add_index :likes, [:user_id, :shop_id], unique: true
end
end
次に各モデルに関連付けを行います。
Shopモデルにおけるlike_users
は後で使うのでひとまずこのような形で記述しておいて下さい。
class User < ApplicationRecord
・・・
has_many :likes, dependent: :destroy
has_many :like_shops, through: :likes, source: :shop
・・・
end
class Shop < ApplicationRecord
・・・
has_many :likes, dependent: :destroy
has_many :like_users, through: :likes, source: :user
・・・
end
Likesコントローラの作成・ルーティング設定
コントローラを作成します。
$ rails g controller Likes
そしてルーティングを設定します。
今回はcreateアクションのみで実装しようと思うのでcreateだけあればOKです。
Rails.application.routes.draw do
・・・
resources :likes, only: [:create]
・・・
end
Likesコントローラ実装
さて、コントローラの中身を実装していきます。
createアクション+toggleメソッドでいいねといいねの解除を表現しています。
いいねの保存や削除が上手く行かなかったら422のレスポンスを返して問題なく機能すれば200のレスポンスを返すというとてもシンプルな構造です。
class LikesController < ApplicationController
def create
@shop = Shop.find(params[:shop_id])
@like = current_user.likes.find_by(shop: @shop)
toggle
end
private
def toggle
if @like
return head :unprocessable_entity unless @like.destroy
else
@like = current_user.likes.build(shop: @shop)
return head :unprocessable_entity unless @like.save
end
head :ok
end
end
ビューの編集
いいねボタンといいね解除ボタンについて記述していきます。
今回はlikes/_like.html.slim
というパーシャルを作ってそこにボタンのビューを書きました。
ポイントを箇条書きします。
- ユーザーがログインしている時だけいいねボタンが表示されるように
user_signed_in?
メソッドを用いて表現しています。 - いいねボタンを押した時にJavaScript(jQuery)が動くように
id="link-mark"
を用意しています。 - Bootstrap4でボタンのスタイリングをしています。
-
data-shop_id="#{@shop.id}"
は後程出てくるlike.js
に対象店舗のid情報を送信するために必要です。 -
like?
メソッドでユーザーが対象の店舗に既にいいねしているかの判断をしています。
- if user_signed_in?
- unless @shop.like?(current_user)
a#link-mark.btn.btn-primary.text-white data-shop_id="#{@shop.id}" いいね!
- else
a#link-mark.btn.btn-secondary.text-white data-shop_id="#{@shop.id}" いいね!を取り消す
そして、like?
メソッドは自作しなければならないので、Shopモデルで実装していきます。
各モデルを関連付けしたことによってinclude?
メソッドが使用できるようになっているので、それを用いてユーザーが対象店舗に既にいいねをしているかしていないかをtrue
またはfalse
で返しています。
class Shop < ApplicationRecord
・・・
has_many :likes, dependent: :destroy
has_many :like_users, through: :likes, source: :user
・・・
def like?(user)
like_users.include?(user)
end
end
like.js
の作成
さて、これでいいねボタンとLikesコントローラのcreateアクションの実装まで終えました。
あとは、いいねボタンを押したらLikesコントローラのcreateアクションで処理が行われるようにJavaScript(jQuery)の実装をしてビューとコントローラを繋いでいけば狙い通りの動きをしてくれるはずです。
そこで、先程のlikeパーシャルの最後に<script>
タグを生やすためにjavascript_include_tag
ヘルパーを用います。
・・・
= javascript_include_tag('like')
javascript_include_tag(like)
はapp/assets/javascripts
フォルダの中にあるlike.js
ファイルを呼出しますよという意味なので、$ touch app/assets/javascripts/like.js
のようにしてファイルを作成します。
そして以下のようにAjax処理を記述してあげれば完成です。
実装内容についてはコメントを付けているのでそちらを参照して下さい。
$(function(){
// id="link-mark"の箇所(いいねボタン)をクリックしたら
$('#link-mark').on('click', function(){
// 非同期でlikes#createに処理を送信+その時に店舗情報(shop_id)を渡す
$.ajax({
url: '/likes',
type: 'POST',
data: {shop_id: $(this).data('shop_id')}
})
// 処理が上手く行ったらボタンを切り替えて
.done((data) => {
if ($(this).text() === 'いいね!') {
$(this).text('いいね!を取り消す').removeClass('btn-primary').addClass('btn-secondary');
} else if ($(this).text() === 'いいね!を取り消す') {
$(this).text('いいね!').removeClass('btn-secondary').addClass('btn-primary');
}
})
// 処理が上手く行かなかったら失敗の旨を伝えるアラートを表示
.fail((data) => {
alert('いいね!に失敗しました');
})
});
});
最終的にはきちんとボタンを押したら非同期でボタンが切り替わって、Likesテーブルにいいねが追加されたり削除されたりするようになります。(このgifではLikesテーブルにいいねが追加されたり削除されたりする様子は分かりませんが。。。笑)
まとめ
-
.js.erb
を用いない方法でいいね機能を実装してみました。 - 色々調べたり聞いたりしながら、RailsにおけるAjax処理の実装方法のパターンを増やせるようになったので大きな収穫でした。