Railsチュートリアルで作成したサンプルアプリに、like(いいね)機能を拡張します。
環境
・Rails 5.1.6
・Railsチュートリアル第4版(Rails5.1) 14章まで実装済み
仕様
・プロフィールにユーザーのlike数が表示される
・like数をクリックすると、likeしたマイクロポスト一覧が表示される
・マイクロポストにlikeボタンとlikeされた回数が表示される
・likeボタンをクリックすると、like数とボタンのlike/unlikeが非同期で更新される
FavoriteRelationshipModel
テーブルの作成
ユーザーは複数のマイクロポストをlikeでき、また、マイクロポストは複数のユーザーからlikeされます。そのため、フォロー機能のときと同じように、中間テーブルを作成して多対多の関係を作っていきます。モデル名は、チュートリアルにならってFavoriteRelationshipとします。このFavoriteRelationshipモデルは、ユーザとマイクロポストを結びつけるため、user_idとmicropost_idを属性として持たせます。まずは以下のコマンドを実行して、マイグレーションを生成します。
rails generate model FavoriteRelationship user_id:integer micropost_id:integer
次に、インデックスを追加します。さらに、user_idとmicropost_idの組み合わせがユニークであるという制約も追加します。
class CreateFavoriteRelationships < ActiveRecord::Migration[5.1]
def change
create_table :favorite_relationships do |t|
t.integer :user_id
t.integer :micropost_id
t.timestamps
end
#indexを追加
add_index :favorite_relationships, :user_id
add_index :favorite_relationships, :micropost_id
add_index :favorite_relationships, [:user_id, :micropost_id], unique: true
end
end
最後に以下のコマンドを実行して、FavoriteRelationshipテーブルを作成します。
rails db:migrate
User/Micropostの関連付け
ユーザーは複数のマイクロポストをlikeでき、マイクロポストは複数のユーザーからlikeされるという多対多の関係を、FavoriteRelationshipモデルを用いて定義します。
まず、ユーザーが、likeしたマイクロポストを複数持つことを、FavoriteRelationshipモデルを用いて以下のように定義します。これにより、user.likes
でそのユーザーがlikeしたマイクロポスト一覧を取得可能になります。
class User < ApplicationRecord
:
:
has_many :favorite_relationships, dependent: :destroy
has_many :likes, through: :favorite_relationships, source: :micropost
:
:
end
同様にして、マイクロポストが、likeしたユーザーを複数持つことを以下のように定義します。これにより、micropost.liked_by
で、そのマイクロポストをlikeしたユーザー一覧が取得できるようになります。
class Micropost < ApplicationRecord
:
:
has_many :favorite_relationships, dependent: :destroy
has_many :liked_by, through: :favorite_relationships, source: :user
:
:
end
最後に、FavoriteRelationshipをユーザーとマイクロポストの両方に属するように定義します。
class FavoriteRelationship < ApplicationRecord
belongs_to :user
belongs_to :micropost
validates :user_id, presence: true
validates :micropost_id, presence: true
end
likes関連のメソッドの追加
マイクロポストのlikeやlike解除を簡単に行えるようにするために、便利なメソッドを追加します。
class User < ApplicationRecord
class UserTest < ActiveSupport::TestCase
:
:
# マイクロポストをライクする
def like(micropost)
likes << micropost
end
# マイクロポストをライク解除する
def unlike(micropost)
favorite_relationships.find_by(micropost_id: micropost.id).destroy
end
# 現在のユーザーがライクしていたらtrueを返す
def likes?(micropost)
likes.include?(micropost)
end
private
:
:
end
これにより、user.like(micropost)
でマイクロポストをlikeし、user.unlike(micropost)
でマイクロポストのlikeを解除することができるようになりました。
likeページの作成
ユーザーのステータス部分にlike数を表示し、そこをクリックするとlikeしたマイクロポスト一覧を表示できるようにします。完成イメージは以下の通りです。
like数の表示
ユーザーのステータス部分に、そのユーザーがlikeしたマイクロポストの総数を表示します。
まずは、以下のようにルーティングを追加します。
:
:
resources :users do
member do
get :following, :followers , :likes
end
end
:
:
これにより、以下のようなルーティングテーブルが生成されます。
HTTPリクエスト | URL | アクション | 名前付きルート |
---|---|---|---|
GET | /users/1/likes | likes | likes_user_path(1) |
次に、統計情報パーシャルに以下を追記することによって、プロフィールページとHomeページのステータス部分にlike数を表示します。
.
.
<a href="<%= likes_user_path(@user) %>">
<strong id="likes" class="stat">
<%= @user.likes.count %>
</strong>
likes
</a>
</div>
これで、ユーザーのステータスにlike数が追加されました。
likeしたマイクロポスト一覧の表示
ユーザーのプロフィール内のlike数をクリックすると、そのユーザーがこれまでにlikeしたマイクロポスト一覧が表示されるようにします。
まず、users_controller.rb
にlikesアクションを追加します。
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
:following, :followers, :likes]
:
:
def likes
@title = "Likes"
@user = User.find(params[:id])
@microposts = @user.likes.paginate(page: params[:page])
render 'show_like'
end
private
:
:
likesアクションでは、@microposts
にユーザーがlikeしているマイクロポストを代入し、show_like
ビューを描画しています。このビューは以下の通り作成します。
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= gravatar_for @user %>
<h1><%= @user.name %></h1>
<span><%= link_to "view my profile", @user %></span>
<span><b>Microposts:</b> <%= @user.microposts.count %></span>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
</aside>
<div class="col-md-8">
<h3><%= @title %></h3>
<% if @user.likes.any? %>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
以上で、likeしたマイクロポスト一覧を表示できるようになりました。
likeボタンの作成
マイクロポストにlikeボタンを表示し、クリックでlike/unlikeの切り替えができるようにします。
likeボタンの表示
likeページのときと同様に、まずはルーティングの追加から始めます。以下のように、FavoriteRelationshipリソース用のルーティングを追加します。
:
:
resources :favorite_relationships, only: [:create, :destroy]
end
次に、micropostパーシャルに、like/unlikeボタンの表示を追加します。以下では、current_userがそのマイクロポストをlikeしているかどうかで、表示するボタンを切り替えています。また、後でAjaxを実装して非同期でボタンを切り替えるために、ユニークなidをspanタグに割り振っています。
:
:
<span class="like" id="like_form_<%= micropost.id %>">
<% if micropost.liked_by.include?(current_user) %>
<%= render "microposts/unlike", micropost: micropost %>
<% else %>
<%= render "microposts/like", micropost: micropost %>
<% end %>
</span>
</li>
likeボタンとunlikeボタンのパーシャルは、それぞれ以下の通りです。ボタンには、Bootstrap3に用意されているglyphiconと呼ばれるアイコン集の中のハートアイコンを用いています。また、likeされた数をmicropost.liked_by.count
で取得し、ハートアイコンの隣に表示しています。
<%= form_for(current_user.favorite_relationships.build) do |f| %>
<div><%= hidden_field_tag :micropost_id, micropost.id %></div>
<%= button_tag(class: "btn btn-default" ) do %>
<%= content_tag :span, micropost.liked_by.count, class: "glyphicon glyphicon-heart-empty" %>
<% end %>
<% end %>
<%= form_for(current_user.favorite_relationships.find_by(micropost_id: micropost.id),
html: { method: :delete }) do |f| %>
<%= button_tag( class: "btn btn-default") do %>
<%= content_tag :span, micropost.liked_by.count, class: "glyphicon glyphicon-heart" %>
<% end %>
<% end %>
最後に、ボタンの配置や色を変更するために、custom.scss
に以下を追記します。
:
:
/* likes */
.glyphicon-heart {
color: red;
}
.btn-default:hover > .glyphicon-heart-empty {
color: red;
}
.btn-default {
position: relative;
left: 60px;
}
ここまでで、マイクロポストにlike/unlikeボタンが表示されました。次は、これを動作させてlike機能を完成させます。
likeボタンの動作
rails generate controller FavoriteRelationships
でFavoriteRelationshipsコントローラーを作成し、コントローラー内にcreateアクションとdestroyアクションを記述します。
class FavoriteRelationshipsController < ApplicationController
before_action :logged_in_user
def create
micropost = Micropost.find(params[:micropost_id])
current_user.like(micropost)
redirect_back(fallback_location: root_url)
end
def destroy
micropost = FavoriteRelationship.find(params[:id]).micropost
current_user.unlike(micropost)
redirect_back(fallback_location: root_url)
end
end
ここまででlike機能が完成しました。次は、これを非同期で動作するように拡張します。
Ajaxによる非同期化
Ajaxを用いて、like/unlikeボタンをクリックした際に、ページを更新せずにlike数の更新とlike/unlikeボタンの切り替えを行うようにします。
まずは、like_form
とunlike_form
に、remote:true
を追加します。
<%= form_for(current_user.favorite_relationships.build, remote: true) do |f| %>
:
:
<%= form_for(current_user.favorite_relationships.find_by(micropost_id: micropost.id),
html: { method: :delete }, remote: true) do |f| %>
:
:
次に、FavoriteRelationshipsコントローラでAjaxリクエストに対応します。
class FavoriteRelationshipsController < ApplicationController
before_action :logged_in_user
def create
@user = current_user
@micropost = Micropost.find(params[:micropost_id])
current_user.like(@micropost)
respond_to do |format|
format.html { redirect_back(fallback_location: root_url) }
format.js
end
end
def destroy
@user = current_user
@micropost = FavoriteRelationship.find(params[:id]).micropost
current_user.unlike(@micropost)
respond_to do |format|
format.html { redirect_back(fallback_location: root_url) }
format.js
end
end
end
最後に、Ajaxリクエストを受信時に呼び出されるJS-ERbファイルを作成します。以下では、likes数の更新と、like/unlikeボタンの切り替えを行なっています。
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/unlike',
micropost: @micropost)) %>");
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/like',
micropost: @micropost)) %>");
以上で完成です。
内容に不備がございましたら、コメントにてお知らせいただければ幸いです。