Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

目標

ユーザーは投稿ができ、さらに投稿に対していいねをすることができるようにする
like_sample.gif

どんな設計にするのか

・ユーザー
→投稿できる
→いいねができる

・投稿
→内容のみ

ページ設計

スクリーンショット 2018-09-02 19.03.15.png

プロジェクトの作成

$ rails new like_sample
$ cd like_sample

Userモデル, Postモデルの作成

モデルを作成していきます。今回はUserはdeviseを使用してモデルを作成していきます。

deviseの導入

Gemfile.
gem 'devise'
$ bundle install
$ rails g devise:install
$ rails g devise user
$ rails g devise:views

postモデルの作成

postモデルには投稿内容contentと、誰が投稿したのかというuser_idが入るようにします。

$ rails g model post content:string user:references
$ rails db:migrate

アソシエーションの確認

今作ったUserモデルとPostモデルは1対多の関係です。
ユーザーそれぞれはたくさんの投稿ができ、投稿それぞれは1人のユーザーによって書かれたものであるというようなイメージです。

user.rb

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  has_many :posts, dependent: :destroy
end

has_many :postsの後に今回はdependent: :destroyをつけました。これはpostがuserに依存している。ここでは、もしユーザーがデータベースから削除されてしまった場合にユーザーがした投稿も全て消えるようになります。

post.rb

モデルの作成時にuser:referencesをつけたのですでにbelongs_to :userが書かれています。

app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

コントローラーの作成

users_controller.rb

ユーザーの一覧ページと詳細ページがあることを考慮して、indexアクションとshowアクションを定義します。

$ rails g controller users index show

posts_controller.rb

投稿も同様にindexアクションとshowアクションを定義します。(createアクションもあるが、ビューが存在しないのでここでは書かない)

$ rails g controller posts index show

ルーティングの作成

ルーティングを考えていきます。

・投稿の一覧と詳細が見れる
・投稿することができる
・ユーザーの一覧と詳細が見れる
ことから以下のようなルーティングにします。

routes.rb

config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  resources :users, only: [:index, :show]
  resources :posts, only: [:index, :show, :create]

  root 'posts#index'

end

コントローラーのアクションとそれに対応するビューを作っていこう

posts_controller.rb

posts_controllerを以下のように変更しよう。

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:show, :create]
  def index
    @posts = Post.all
    @post = Post.new
  end

  def show
    @post = Post.find(params[:id])
  end

  def create
    @post = Post.new(post_params)
    @post.user_id = current_user.id
    if @post.save
      redirect_back(fallback_location: root_path)
    else
      redirect_back(fallback_location: root_path)
    end
  end

  private
  def post_params
    params.require(:post).permit(:content)
  end
end

ログインしていないユーザーはshow, createは実行できないようになりました。

次はビューを作っていきます。

posts/index.html.erb

app/views/posts/index.html.erb
<h1>いいねサンプル</h1>
<% if user_signed_in? %>
  <%= link_to "ログアウト", destroy_user_session_path, :method => :delete %>
  <%= link_to "マイページへ", user_path(current_user.id) %>
  <h2>投稿する</h2>
  <%= form_for @post do |f| %>
    <%= f.text_field :content %>
    <%= f.submit %>
  <% end %>
  <hr>
  <h2>投稿一覧</h2>
  <% @posts.each do |post| %>
    <a href="/users/<%= post.user.id %>"><%= post.user.email %></a>
    <p><a href="/posts/<%= post.id %>"><%= post.content %></a></p>
  <% end %>

<% else %>
  <%= link_to "ユーザー登録", new_user_registration_path %>
  <%= link_to "ログイン", new_user_session_path %>
<% end %>

posts/show.html.erb

app/views/posts/show.html.erb
<h1>投稿詳細ページ</h1>
<h3><%= @post.user.email %></h3>
<h3><%= @post.content %></h3>
<%= link_to "ホームへ戻る", posts_path %>

users_controller.rb

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end
end

users/index.html.erb

app/views/users/index.html.erb
<h1>ユーザー一覧</h1>
<% @users.each do |user| %>
  <a href="/users/<%= user.id %>"><%= user.email %></a>
  <hr>
<% end %>
<%= link_to "ホームへ戻る", posts_path %>

users/show.html.erb

app/views/posts/show.html.erb
<h1>ユーザー詳細ページ</h1>
<h3><%= @user.email %></h3>
<h2>投稿内容</h2>
<% @user.posts.each do |post| %>
  <a href="/posts/<%= post.id %>"><%= post.content %></a>
  <hr>
<% end %>
<%= link_to "ユーザー一覧へ", users_path %>
<%= link_to "ホームへ戻る", posts_path %>

Likeモデルをつくろう

これからやっといいね機能を実装していきます。
いいね機能は、誰がどの投稿に対していいねをしたのかという情報をLikeテーブルというUserテーブルとPostテーブルの中間テーブルに格納していきます。

$ rails g model like post:references user:references
$ rails db:migrate

アソシエーションの確認

中間テーブルであるLikeテーブルを介してユーザーがどの投稿にいいねをしているのか、逆に投稿がどのユーザーにいいねされているのかを簡単に取得できるようにするために、liked_postliked_usersなるものを作ります。

user.rb

app/models/post.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  has_many :posts, dependent: :destroy
  has_many :likes, dependent: :destroy
  has_many :liked_posts, through: :likes, source: :post
end

liked_postsによってuserがどの投稿をいいねしているのかを簡単に取得できるようになります。

post.rb

app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :likes
  has_many :liked_users, through: :likes, source: :user
end

liked_usersによって投稿が誰にいいねされているのかを簡単に取得できるようになります。

likes_controllerの作成

$ rails g controller likes

likeに関してはビューを持たない(投稿の詳細ページで完結する)のでアクションはあとからつけていきます。
・いいねをする→likes#create
→Likeモデルにuser_idとpost_idを格納
いいねを取り消す→likes#destroy
→Likeモデルのレコードを削除する

バリデーションをつけよう

いいね機能にバリデーションをつけます。具体的に何をするのかというと、1人が1つの投稿に対して、1つしかいいねをつけられないようにします。(今回は自分の投稿にもいいねができます)

like.rb

app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
  validates_uniqueness_of :post_id, scope: :user_id
end

validates_uniqueness_ofによって、post_idとuser_id の組が1組しかないようにバリデーションをかけました。

ルーティングの作成

routes.rb

config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  resources :users, only: [:index, :show]
  resources :posts, only: [:index, :show, :create] do
    resources :likes, only: [:create, :destroy]
  end

  root 'posts#index'

end

コントローラーのアクションを作っていこう

likes_controller.rb

app/controllers/likes_controller.rb
class LikesController < ApplicationController
  def create
    @like = current_user.likes.create(post_id: params[:post_id])
    redirect_back(fallback_location: root_path)
  end

  def destroy
    @like = Like.find_by(post_id: params[:post_id], user_id: current_user.id)
    @like.destroy
    redirect_back(fallback_location: root_path)
  end
end

投稿の詳細ページでいいねをできるようにするので、posts_controllerのshowアクションに内容を追加していきます。

posts_controller.rb

app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:show, :create]
  def index
    @posts = Post.all
    @post = Post.new
  end

  def show
    @post = Post.find(params[:id])
    @like = Like.new
  end

  def create
    @post = Post.new(post_params)
    @post.user_id = current_user.id
    if @post.save
      redirect_back(fallback_location: root_path)
    else
      redirect_back(fallback_location: root_path)
    end
  end

  private
  def post_params
    params.require(:post).permit(:content)
  end
end

いいねしているかどうかの判定

また、ユーザーが投稿に対して、すでにいいねをしているのかどうかを判定することができるようにalready_liked?を定義します。

user.rb

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  has_many :posts, dependent: :destroy
  has_many :likes, dependent: :destroy
  has_many :liked_posts, through: :likes, source: :post
  def already_liked?(post)
    self.likes.exists?(post_id: post.id)
  end
end

ビューを変更しよう

posts/show.html.erb

投稿詳細ページでいいねをできるようなビューに変更していきます。
内容としては
・いいねしている時→「いいねを取り消す」ボタンを表示
・いいねをしてない時→「いいね」ボタンを表示
・いいねの件数の表示
・投稿に対して誰がいいねしたのかを表示

app/views/posts/show.html.erb
<h1>投稿詳細ページ</h1>
<h3><%= @post.user.email %></h3>
<h3><%= @post.content %></h3>
<h3>いいね件数: <%= @post.likes.count %></h3>
<% if current_user.already_liked?(@post) %>
  <%= button_to 'いいねを取り消す', post_like_path(@post), method: :delete %>
<% else %>
  <%= button_to 'いいね', post_likes_path(@post) %>
<% end %>
<h2>いいねしたユーザー</h2>
<% @post.liked_users.each do |user| %>
  <li><%= user.email %></li>
<% end %>

<%= link_to "ホームへ戻る", posts_path %>

users/show.html.erb

次にユーザーの詳細ページでそのユーザーが誰に対していいねしているのかどうかを表示できるようにします。

app/views/users/show.html.erb
<h1>ユーザー詳細ページ</h1>
<h3><%= @user.email %></h3>
<h2>投稿内容</h2>
<% @user.posts.each do |post| %>
  <a href="/posts/<%= post.id %>"><%= post.content %></a>
  <hr>
<% end %>
<h3>いいねしている投稿</h3>
<% @user.liked_posts.each do |post| %>
  <%= post.user.email %>
  <%= post.content %>
<% end %>
<%= link_to "ユーザー一覧へ", users_path %>
<%= link_to "ホームへ戻る", posts_path %>

posts/index.html.erb

投稿一覧ページでもいいねの数を見れるようにしましょう。

app/views/posts/index.html.erb
<h1>いいねサンプル</h1>
<% if user_signed_in? %>
  <%= link_to "ログアウト", destroy_user_session_path, :method => :delete %>
  <%= link_to "マイページへ", user_path(current_user.id) %>
  <%= link_to "ユーザー一覧へ", users_path %>
  <h2>投稿する</h2>
  <%= form_for @post do |f| %>
    <%= f.text_field :content %>
    <%= f.submit %>
  <% end %>
  <hr>
  <h2>投稿一覧</h2>
  <% @posts.each do |post| %>
    <a href="/users/<%= post.user.id %>"><%= post.user.email %></a>
    <a href="/posts/<%= post.id %>"><%= post.content %></a>
    (<%= post.liked_users.count %>)
  <% end %>

<% else %>
  <%= link_to "ユーザー登録", new_user_registration_path %>
  <%= link_to "ログイン", new_user_session_path %>
<% end %>

users/index.html.erb

最後にユーザーの一覧を表示しましょう。

app/views/users/index.html.erb
<h1>ユーザー一覧</h1>
<% @users.each do |user| %>
  <a href="/users/<%= user.id %>"><%= user.email %></a>
  <hr>
<% end %>
<%= link_to "ホームへ戻る", posts_path %>

以上です

これでユーザーが投稿に対していいねをすることができるようになりました。
デザインなど加えてより見やすくしてください

ソースコードはこちらから

https://github.com/Toomoya/Like_Sample

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした