はじめに
- こんな感じのAjaxで「お気に入り」のオンオフできる(ついでにカウントも)リストを作ります。
-
Devise
を使ってログインしている状態を想定しているので、Deviseをある程度知っている人向けです。
今回使うライブラリ
- Devise(ログイン処理。)
- Toastr.JS(通知メッセージ)
- CounterCulture(お気に入りカウント)
- FontAwesomeRails(フリーアイコン)
- DeviseとCounterCulture、FontAwesomeRailsはGemなので、READMEに従って入れましょう。
- Toastrだけは自分でJSとCSSファイルをダウンロードしてきて、
vendor/assets/toastr
ディレクトリ配下に配置しましょう。パイプラインでの設定を忘れずに。
必要な予備知識
- RailsのAjax通信。
- BootStrap4(ビューの整形に使っているだけなので、詳しくなくていい)
- Slimテンプレート(2と同様)
- 使われているライブラリの知識
環境
$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux]
$ rails -v
Rails 5.2.1
実装
モデル
- 今回は
User
とDog
、Like
モデルを使います。 -
User
モデルはDevise
で作成します。 -
Like
を中間テーブルとして、User
とDog
を多対多の関連づけすることで、いいねリレーションを構築します。 -
seed-fu
とかを使って、前もってページネーション用のインスタンスをDBに作っておきましょう。
# マイグレーション
class CreateDogs < ActiveRecord::Migration[5.2]
def change
create_table :dogs do |t|
t.string :name, null: false, default: ''
t.integer :receive_likes_count, null: false, default: 0
end
end
end
class CreateLikes < ActiveRecord::Migration[5.2]
def change
create_table :likes do |t|
t.references :user
t.references :dog
end
end
end
class User < ApplicationRecord
has_many :likes, dependent: :destroy
end
class Dog < ApplicationRecord
has_many :receive_likes, dependent: :destroy, class_name: 'Like'
# 特定のユーザーからお気に入りされているか
def liked_by?(user)
user.likes.exists?(dog_id: id)
end
# Ajax実行時に、DOMエレメントを特定するID
def id_attribute
"dog-#{id}"
end
end
class Like < ApplicationRecord
belongs_to :user
belongs_to :dog
# counter culture設定
counter_culture :dog, column_name: 'receive_likes_count'
end
コントローラー
- 今回のページを
sample#index
アクションとします。 - 「いいね」しても順番が表示の順番が変わらないよう、
order
を設定しておきます。
class SampleController < ApplicationController
def index
@dogs = Dog.all.order(created_at: :asc)
end
end
- また、いいねをAjaxでオンオフするためのAPIコントローラー
api/like_controller.rb
も追加します。 - このコントローラー呼び出し時に実行されるJSファイルは後ほど記載します。
class Api::LikeController < ApplicationController
before_action :setup_dog!
before_action :setup_like!
def create
@like.save
end
def destroy
@like.destroy
end
private
def setup_dog!
@dog = Dog.find(params[:dog_id])
end
def setup_like!
# create時はまだ存在していないのでinitializeされ、
# destroy時は既に存在しているのでfindされる。
@like = current_user
.likes
.find_or_initialize_by(dog_id: @dog.id)
end
end
- ルーティングはこんな感じ。
namespace :api do
post 'like/:dog_id' => 'like#create', as: :like
delete 'like/:dog_id' => 'like#destroy', as: :unlike
end
ヘルパー
- いいねをオンオフするボタンをレンダーするヘルパーメソッドを作成する。
- これにより、ログイン中のユーザーが特定の
Dog
モデルをお気に入りしたかどうかを判定して、アイコンとリンクを出し分けることができる。
module MyHelper
def toggle_like_button(dog)
# ログインしていない場合
return fa_icon 'heart-o', text: dog.receive_likes_count if current_user.blank?
liked = dog.liked_by?(current_user)
icon = fa_icon (liked ? 'heart' : 'heart-o'), text: dog.reload.receive_likes_count
if liked
# お気に入りを外すボタン
link_to icon, api_unlike_path(dog_id: dog.id), remote: true, method: :delete
else
# お気に入りするボタン
link_to icon, api_like_path(dog_id: dog.id), remote: true, method: :post
end
end
end
ビュー
- 今回用意するビューは、大元のページ用と、Dogのパーシャルビューの2つです。
# ページ
app/views/sample/
├── _dog.html.slim
└── index.html.slim
/ index.html.slim
.container.my-5
.row
.col-md-6.offset-md-3
#project-list(style='overflow-y: scroll; height: 80vh;')
= render @dogs
/ _dog.html.slim
.card.card-body.mb-3(id="#{dog.id_attribute}")
/ この.like要素をAjaxで書き換える
.like
= toggle_like_button(dog)
h3.card-title = dog.name
JSファイル
- 最後にlikeコントローラー呼び出し時に呼び出されるJSファイルを追加します。
app/views/api/like/
├── create.js.erb
└── destroy.js.erb
// create.js.erb
// toastrの通知処理
toastr.success("<%= "#{@like.dog.name}をお気に入りに追加しました" %>")
// 特定のLIKEボタンのDOMを入れ替える。
$("<%= "##{@dog.id_attribute} .like" %>").html("<%= j toggle_like_badge(@dog) %>")
// destroy.js.erb
toastr.error("<%= "#{@dog.name}をお気に入りから削除しました" %>")
$("<%= "##{@dog.id_attribute} .like" %>").html("<%= j toggle_like_badge(@dog) %>")
おわりに
- かけあしでしたが、ひとまずこれで「お気に入り処理」ができるようになりました。
- いろんな便利なライブラリがあるっていいですね。