Rails
jQuery
Ajax

【Rails】いいね機能の実装(.js.erbを用いない方法)【プログラミング学習165日目】


はじめに

今回は、ポートフォリオにいいね機能を実装してみて色々と苦労したので備忘録も兼ねて記事にしていきたいと思います。

「Rails いいね 実装」などでググると以下の記事を始めとして色々な方が解説してくれている記事を見つけることができます。

- Ruby on Rails いいね機能の実装と解説(Rails Tutorial 14章 演習の機能拡張)

- [Rails]いいね機能の非同期での実装!!!

- Railsでいいね機能を実装しよう

Railsチュートリアルライクな実装方法はこれらを見れば理解できたので、これらの記事と少し方法を変えてみたいなというモチベーションから.js.erbを用いない方法でいいね機能を実装してみました。


前提


  • スパイスカレー屋の検索・共有アプリを作成しており、UserがShopにいいねできるようにしたいという状況を想定しています。

  • 既にUserリソースとShopリソースは作成されている状態から始めます。

  • 認証系にはDeviseを用いており、current_userメソッドやuser_signed_in?メソッド等が使用できる状態であるとします。

  • ビューは.erbではなく、.slimを使って書きます。

  • 本記事ではいいねボタンはBootstrap4で簡単にスタイリングします。

  • 最終的には以下のように動くことを狙います。

ダウンロード (1).gif


開発環境


  • Ruby 2.5.1

  • Rails 5.2.2


いいね実装の具体的な流れ


必要なGemのインストール

jQueryが使えるようにあらかじめ'jquery-rails'を入れておきます。


Gemfile

gem 'jquery-rails'


そしてapplication.jsを以下のようにしておきます。

(順番に気を付けないとコンソールでエラーが消えてくれないので注意です。)


app/assets/javascripts/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と関連付いていると認識してくれるようになるので便利です。


app/models/like.rb

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を実行します。


db/migrate/[timestamp]_create_likes.rb

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は後で使うのでひとまずこのような形で記述しておいて下さい。


app/models/user.rb

class User < ApplicationRecord

・・・
has_many :likes, dependent: :destroy
has_many :like_shops, through: :likes, source: :shop
・・・
end


app/models/shop.rb

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のレスポンスを返すというとてもシンプルな構造です。


app/controllers/likes_controller.rb

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?メソッドでユーザーが対象の店舗に既にいいねしているかの判断をしています。


app/views/likes/_like.html.slim

- 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で返しています。


app/models/shop.rb

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ヘルパーを用います。


app/views/likes/_like.html.slim

・・・

= javascript_include_tag('like')

javascript_include_tag(like)app/assets/javascriptsフォルダの中にあるlike.jsファイルを呼出しますよという意味なので、$ touch app/assets/javascripts/like.jsのようにしてファイルを作成します。

そして以下のようにAjax処理を記述してあげれば完成です。

実装内容についてはコメントを付けているのでそちらを参照して下さい。


app/assets/javascripts/like.js

$(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テーブルにいいねが追加されたり削除されたりする様子は分かりませんが。。。笑)

ダウンロード (1).gif


まとめ



  • .js.erbを用いない方法でいいね機能を実装してみました。

  • 色々調べたり聞いたりしながら、RailsにおけるAjax処理の実装方法のパターンを増やせるようになったので大きな収穫でした。


参考URL