Edited at

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


目標

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

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