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

Rails いいね機能のミニアプリを作ろう

ミニアプリの内容

ユーザーは投稿ができ、投稿に対しいいねが出来るようにする

今回のミニアプリの設計

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

  • 投稿
    →内容のみ

Railsでプロジェクトを作成しよう

$ rails new like_sample
$ cd like_sample

(バージョン 6.0.2で僕は作成しています)

Userモデルの作成

gem deviseを使ってUserモデルを作成します

deviseの導入

Gemfile.
gem 'devise'

gem fileの最下部にdeviseを記載し、bundle installします
deviseはinstallコマンドを忘れずに。

$ 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_contoroller.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/controller/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/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/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/model/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
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組しかないようにバリデーションをかけました。

ルーテイングの作成

cofig/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

likeコントローラーを作ろう

app/controller/likes_controoler.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アクションに内容を追加していきます。

post_controller.rb

app/controllers/post_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/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 %>

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

Image from Gyazo

以上です

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

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

https://github.com/higakikeita/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
ユーザーは見つかりませんでした