LoginSignup
9
12

More than 5 years have passed since last update.

Railsチュートリアルのサンプルアプリにlike(いいね)機能を拡張する

Last updated at Posted at 2019-03-30

Railsチュートリアルで作成したサンプルアプリに、like(いいね)機能を拡張します。

環境

・Rails 5.1.6
・Railsチュートリアル第4版(Rails5.1) 14章まで実装済み

仕様

・プロフィールにユーザーのlike数が表示される
・like数をクリックすると、likeしたマイクロポスト一覧が表示される
・マイクロポストにlikeボタンとlikeされた回数が表示される
・likeボタンをクリックすると、like数とボタンのlike/unlikeが非同期で更新される

完成版の動作は以下の通りです。
動作イメージ(軽量).gif

FavoriteRelationshipModel

テーブルの作成

ユーザーは複数のマイクロポストをlikeでき、また、マイクロポストは複数のユーザーからlikeされます。そのため、フォロー機能のときと同じように、中間テーブルを作成して多対多の関係を作っていきます。モデル名は、チュートリアルにならってFavoriteRelationshipとします。このFavoriteRelationshipモデルは、ユーザとマイクロポストを結びつけるため、user_idとmicropost_idを属性として持たせます。まずは以下のコマンドを実行して、マイグレーションを生成します。
rails generate model FavoriteRelationship user_id:integer micropost_id:integer

次に、インデックスを追加します。さらに、user_idとmicropost_idの組み合わせがユニークであるという制約も追加します。

/sample_app/db/migrate/[timestamp]_create_favorite_relationships.rb
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したマイクロポスト一覧を取得可能になります。

/sample_app/app/models/user.rb
class User < ApplicationRecord
  :
  :
  has_many :favorite_relationships, dependent: :destroy
  has_many :likes, through: :favorite_relationships, source: :micropost
  :
  :
end

同様にして、マイクロポストが、likeしたユーザーを複数持つことを以下のように定義します。これにより、micropost.liked_byで、そのマイクロポストをlikeしたユーザー一覧が取得できるようになります。

/sample_app/app/models/micropost.rb
class Micropost < ApplicationRecord
  :
  :
  has_many :favorite_relationships, dependent: :destroy
  has_many :liked_by, through: :favorite_relationships, source: :user
  :
  :  
end

最後に、FavoriteRelationshipをユーザーとマイクロポストの両方に属するように定義します。

/sample_app/app/models/favorite_relationship.rb
class FavoriteRelationship < ApplicationRecord
  belongs_to :user
  belongs_to :micropost
  validates :user_id, presence: true
  validates :micropost_id, presence: true
end

likes関連のメソッドの追加

マイクロポストのlikeやlike解除を簡単に行えるようにするために、便利なメソッドを追加します。

/sample_app/app/models/user.rb
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ページ.png

like数の表示

ユーザーのステータス部分に、そのユーザーがlikeしたマイクロポストの総数を表示します。
まずは、以下のようにルーティングを追加します。

/sample_app/config/routes.rb
:
:
resources :users do
  member do
    get :following, :followers , :likes
  end
end
:
:

これにより、以下のようなルーティングテーブルが生成されます。

HTTPリクエスト URL アクション 名前付きルート
GET /users/1/likes likes likes_user_path(1)

次に、統計情報パーシャルに以下を追記することによって、プロフィールページとHomeページのステータス部分にlike数を表示します。

/sample_app/app/views/shared/_stats.html.erb
.
.
<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アクションを追加します。

/sample_app/app/controllers/users_controller.rb
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ビューを描画しています。このビューは以下の通り作成します。

/sample_app/app/views/users/show_like.html.erb
<% 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リソース用のルーティングを追加します。

/sample_app/config/routes.rb
:
:
resources :favorite_relationships, only: [:create, :destroy]
end

次に、micropostパーシャルに、like/unlikeボタンの表示を追加します。以下では、current_userがそのマイクロポストをlikeしているかどうかで、表示するボタンを切り替えています。また、後でAjaxを実装して非同期でボタンを切り替えるために、ユニークなidをspanタグに割り振っています。

/sample_app/app/views/microposts/_micropost.html.erb
:
:
  <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で取得し、ハートアイコンの隣に表示しています。

/sample_app/app/views/microposts/_like.html.erb
<%= 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 %>
/sample_app/app/views/microposts/_unlike.html.erb
<%= 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に以下を追記します。

/sample_app/app/assets/stylesheets/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アクションを記述します。

/sample_app/app/controllers/favorite_relationships_controller.rb
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_formunlike_formに、remote:trueを追加します。

/sample_app/app/views/microposts/_like_form.html.erb
<%= form_for(current_user.favorite_relationships.build, remote: true) do |f| %>
:
:
/sample_app/app/views/microposts/_unlike_form.html.erb
<%= form_for(current_user.favorite_relationships.find_by(micropost_id: micropost.id),
             html: { method: :delete }, remote: true) do |f| %>
:
:

次に、FavoriteRelationshipsコントローラでAjaxリクエストに対応します。

/sample_app/app/controllers/favorite_relationships_controller.rb
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ボタンの切り替えを行なっています。

/sample_app/app/views/favorite_relationships/create.js.erb
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/unlike',
                micropost: @micropost)) %>");
/sample_app/app/views/favorite_relationships/destroy.js.erb
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/like',
                micropost: @micropost)) %>");

以上で完成です。
内容に不備がございましたら、コメントにてお知らせいただければ幸いです。

9
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
12