【Rails】いいね機能
本の投稿サイトにいいね機能を追加します。
実装機能の要件
実装機能の要件は以下の通りです。
コントローラ | アクション | 用途 |
---|---|---|
favorites | create | いいねを作成する |
favorites | destroy | いいねを削除する |
モデル | 用途 | 備考 |
---|---|---|
favorite | ユーザーと投稿のセットでいいねをしている状態とする | ユーザーは一つの投稿に一つしかいいねできないこと |
ビュー | 画面 | 要素 |
---|---|---|
投稿一覧画面 | いいね数表示 | |
いいね(する, 外す)ボタン | ||
投稿詳細画面 | いいね数表示 | |
いいね(する, 外す)ボタン | ||
いいね作成ボタン | いいねされていない投稿に対して表示 | |
いいね削除ボタン | いいねされている投稿に対して表示 |
いいね機能のテーブル設計
以下のような、テーブル設計にします。
カラム名 | データ型 | カラムの説明 |
---|---|---|
id | integer | 「いいね」ごとのID |
user_id | integer | 「いいね」したユーザのID |
book_id | integer | 「いいね」された投稿のID |
Model作成
ターミナルでの入力方法は以下の通りです。
-
Favorite
モデルの生成
rails g model Favorite user_id:integer book_id:integer
上記のコマンドを実行すると、db/migrate
ディレクトリにマイグレーションファイルが生成されます。
- データベースへのマイグレーション実行
rails db:migrate
上記のコマンドを実行することで、マイグレーションファイルに基づいてデータベースにテーブルが作成されます。
以上の手順により、Favorite
モデルが作成され、user_id
とbook_id
のカラムが含まれるテーブルがデータベースに作成されます。
関連付けの設定
Favorite
モデルと他のモデルの関連付けを設定します。
モデルの関係性
図表で表すと以下の通りです。
-
User
(ユーザー)とFavorite
(いいね)の関係は、1対多の関係です。1人のユーザーが複数のいいねを持つことができます。 -
Book
(投稿)とFavorite
(いいね)の関係も、1対多の関係です。1つの投稿が複数のいいねを持つことができます。
つまり、ユーザーといいねの関係は「1:N」であり、投稿といいねの関係も「1:N」です。
関連付けの実装
以下のようにそれぞれのモデルに関連付けを記述します。
- Favoriteモデル
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :book
end
上記のコードでは、Favorite
モデルがUser
モデルとBook
モデルに対してそれぞれ1対多の関連付けを持つことを表しています。
- Userモデル
class User < ApplicationRecord
+ has_many :favorites, dependent: :destroy
end
上記のコードでは、User
モデルにhas_many :favorites
の関連付けを定義しています。これによりUser
モデルのインスタンスは複数のFavorite
モデルのインスタンスと関連付けられます。
dependen: :destroy
オプションでUser
が削除された場合に関連するFavorite
も同時に削除できます。
- Bookモデル
class Book < ApplicationRecord
+ has_many :favorites, dependent: :destroy
:
+ def favorited_by?(user)
+ favorites.exists?(user_id: user.id)
+ end
end
上記のコードでは、Book
モデルにも同様にhas_many :favorites
の関連付けを定義しています。これにより、Book
モデルのインスタンスは複数のFavorite
モデルのインスタンスと関連付けられます。また、dependent: :destroy
オプションによって、Book
が削除された場合に関連するFavorite
も同時に削除されるように設定されています。
このような関連付けの設定により、User
モデルとBook
モデルはそれぞれ複数のFavorite
モデルと関連付けられ、1対多の関係が成立します。つまり、1つのユーザは複数のお気に入りを持つことができ、また1つの投稿は複数のお気に入りを受けることができるという関係性が構築されます。
-
def favorited_by?(user)
メソッド
このコードは、Book
モデルにfavorited_by?(user)
メソッドを追加しています。このメソッドは、指定されたユーザが特定の投稿(Book
インスタンス)をいいねしているかどうかを判定します。
以下に、メソッドの内容を解説します。
-
def favorited_by?(user)
:
- メソッドの定義です。
favorited_by?
はメソッド名であり、user
は引数として渡されるユーザオブジェクトです。
-
favorites.exists?(user_id: user.id)
:
-
favorites
は、Book
モデルとFavorite
モデルの関連性によって定義されたアソシエーションです。has_many :favorites
によって、Book
は複数のFavorite
を持つ関連性が設定されています。 -
exists?
メソッドは、与えられた条件に合致するレコードが存在するかどうかをチェックします。 -
user_id: user.id
という条件は、favorites
テーブルのuser_id
カラムがuser.id
(引数で渡されたユーザのID)と一致するかどうかを検証します。 -
favorites
テーブルに、user_id
がuser.id
と一致するレコードが存在する場合は、exists?
メソッドはtrue
を返します。それ以外の場合はfalse
を返します。
このメソッドを使うと、ある投稿(Book
インスタンス)が特定のユーザによっていいねされているかどうかを簡潔に判定できます。もし指定されたユーザがいいねしている場合はtrue
を、していない場合はfalse
を返します。
Routing
以下の通りルーティングの設定をします。
Rails.application.routes.draw do
:
+ resources :books, only: [:index, :show, :edit, :create, :destroy, :update] do
+ resource :favorites, only: [:create, :destroy]
+ end
resources :users, only: [:index, :show, :edit, :update]
end
resources vs resource
-
resources :books
やresources :users
などのように複数形のリソース名を指定すると、URLには:id
が含まれます。これは、それぞれのリソースの特定の要素(レコード)を操作するために識別子(ID)が必要な場合に使用します。 -
一方、
resource :favorites
のように単数形のリソース名を指定すると、URLには:id
が含まれません。これは、そのリソースが他のリソースにネスト(関連付け)されており、ネストされたリソースによって特定できる場合に使用します。 -
ネスト(関連付け)は、異なるリソース間の親子関係を表現するための仕組みです。例えば、ユーザーが投稿に対して1つのコメントしかできない場合、コメントはユーザーと投稿に関連付けられます。この関連付けにより、どのユーザーがどの投稿にコメントしたのかを特定することができます。
-
いいね機能の場合、1人のユーザーは1つの投稿に対して1回しかいいねできないという制約があります。そのため、いいねの削除を行う際には、ユーザーIDと投稿IDがわかれば、どのいいねを削除するかを特定できます。したがって、いいねのIDをURLに含める必要はありません。
-
resources
とresource
の違いは、URLに:id
を含めるかどうかです。resources
を使用すると:id
が含まれ、特定の要素を操作するためのIDが必要です。一方、resource
を使用すると:id
が含まれず、他のリソースとの関連付けによって特定できます。 -
resource
は、自身のIDが分からなくても他のリソースのIDから特定できる場合に使用されます。例えば、いいねの削除の場合、ユーザーIDと投稿IDを指定することで特定できるため、いいねのIDをURLに含める必要がありません。
Controller作成
まず、ターミナルで以下のコマンドを実行してコントローラを作成します。
rails g controller favorites
次に、favorites_controller.rb
ファイルに以下のコードを記述します。
class FavoritesController < ApplicationController
def create
book = Book.find(params[:book_id])
favorite = current_user.favorites.new(book_id: book.id)
favorite.save
redirect_to request.referer
end
def destroy
book = Book.find(params[:book_id])
favorite = current_user.favorites.find_by(book_id: book.id)
favorite.destroy
redirect_to request.referer
end
end
create
メソッドはいいねを作成し、destroy
メソッドはいいねを削除します。
コードについて詳細を説明します。
-
favorite = current_user.favorites.new(book_id: book.id)
:-
current_user
は現在ログインしているユーザーを表します。 -
current_user.favorites
は、current_user
に関連付けられたFavoriteモデルのコレクション(複数のいいね)を表します。 -
favorites.new
は、Favoriteモデルの新しいインスタンスを作成します。このインスタンスは、まだデータベースに保存されていない状態です。 -
book_id: book.id
は、新しいFavoriteインスタンスのbook_id
属性に、指定されたbook.id
をセットしています。これにより、作成されるいいねが特定のBookオブジェクトに関連付けられます。 -
favorite
には、新しく作成されたFavoriteインスタンスが代入されます。
-
-
favorite = current_user.favorites.find_by(book_id: book.id)
:-
favorites.find_by(book_id: book.id)
は、book_id
属性が指定されたbook.id
と一致するFavoriteインスタンスを検索します。このメソッドは、最初に見つかったインスタンスを返します。 -
favorite
には、見つかったFavoriteインスタンスが代入されます。もし一致するインスタンスが見つからなかった場合は、nil
が代入されます。
-
-
redirect_to request.referer
:-
request.referer
は、リクエストを送信した元のページのURLを表します。つまり、ユーザーがいいねの作成または削除を行った元のページに戻ることができます。
-
Viewページ
- 今回は部分テンプレートファイルにするので、ファイルを作成し記述します。
<% if book.favorited_by?(current_user) %>
<%= link_to book_favorites_path(book), method: :delete do %>
<i class="fas fa-heart" aria-hidden="true" style="color: red;"></i>
<%= book.favorites.count %> いいね
<% end %>
<% else %>
<%= link_to book_favorites_path(book), method: :post do %>
<i class="fas fa-heart" aria-hidden="true"></i>
<%= book.favorites.count %> いいね
<% end %>
<% end %>
コードの解説
このコードは、条件分岐に基づいて表示内容を切り替えています。
-
<% if book.favorited_by?(current_user) %>
:-
book.favorited_by?(current_user)
は、book
オブジェクトが現在のユーザー (current_user
) によっていいねされているかどうかを確認するメソッドです。 - もし現在のユーザーによっていいねされている場合は、
if
文の中のブロックが実行されます。
-
-
<%= link_to book_favorites_path(book), method: :delete do %>
:-
link_to
は、指定されたURLへのリンクを生成するためのRailsのヘルパーメソッドです。 -
book_favorites_path(book)
は、book
オブジェクトに対していいねを作成・削除するためのURLを生成します。 -
method: :delete
は、リンクをクリックした際にHTTPのDELETEメソッドが使用されるように指定しています。つまり、いいねを削除するアクションに対応します。
-
-
<i class="fas fa-heart" aria-hidden="true" style="color: red;"></i>
:-
<i>
タグは、Font Awesomeアイコンを表示するためのHTML要素です。 -
fas fa-heart
は、Font Awesomeのハートアイコンを指定しています。 -
style="color: red;"
は、アイコンの色を赤に指定しています。この部分はいいねされている場合に適用されます。
-
-
<%= book.favorites.count %> いいね
:-
book.favorites.count
は、book
オブジェクトに紐づくいいねの数を取得します。
-
-
<% else %>
:-
if
文の条件に合致しない場合に実行されるブロックです。
-
-
<%= link_to book_favorites_path(book), method: :post do %>
:-
book_favorites_path(book)
は、book
オブジェクトに対していいねを作成するためのURLを生成します。 -
method: :post
は、リンクをクリックした際にHTTPのPOSTメソッドが使用されるように指定しています。つまり、いいねを作成するアクションに対応します。
-
-
<i class="fas fa-heart" aria-hidden="true"></i>
:- こちらも先ほどと同様に、ハートアイコンを表示するためのHTML要素です。
-
<%= book.favorites.count %> いいね
:- 先ほどと同様に、いいねの数を表示します。
- 部分テンプレートファイルを①投稿一覧画面と②投稿詳細画面から呼び出します。
投稿一覧画面
<table class="table table-hover table-inverse">
<thread>
<tr>
<th></th>
<th>Title</th>
<th>Opinion</th>
<th colspan="3"></th>
</tr>
</thread>
<tbody>
<% books.each do |book| %>
<tr>
<td><%= link_to book.user do %>
<%= image_tag book.user.get_profile_image, size:'50x50' %>
<% end %>
</td>
<td><%= link_to book.title,book %></td>
<td><%= book.body %></td>
+ <td><%= render "favorites/btn", book: book %></td>
</tr>
<% end %>
</tbody>
</table>
投稿詳細画面
<div class="container">
<div class="row">
<div class="col-md-3">
<h2>User info</h2>
<%= render "users/info", user: @user %>
<h2 class="mt-3">New book</h2>
<%= render "form", book: @books %>
</div>
<div class='col-md-8 offset-md-1'>
<h2>Book detail</h2>
<table class="table">
<tr>
<td><%= link_to @book.user do %>
<%= image_tag @book.user.get_profile_image, size:"100x100" %><br>
<%= @book.user.name %>
<% end %>
</td>
<td><%= link_to @book.title, @book %></td>
<td><%= @book.body %></td>
+ <td><%= render "favorites/btn", book: @book %></td>
<% if @book.user == current_user %>
<td><%= link_to "Edit", edit_book_path(@book), class: "btn btn-sm btn-success" %></td>
<td><%= link_to "Destroy", @book, method: :delete, data: { confirm: "本当に消しますか?" }, class: "btn btn-sm btn-danger" %></td>
<% end %>
</tr>
</table>
</div>
</div>
</div>
以上、いいね機能の実装でした。
息子が今とても流行っているヘルパンギーナというウイルスに感染していることがわかり、40度の高熱がでてしまいました💦
これが2~3日続くみたい、、、小さな身体で一生懸命治そうとがんばっている姿を見ると涙がでます。
絶対元気になるから大丈夫だよ。
今週は妻と協力しながら息子の看病をします。
Discussion
もし宜しければ、railsをAPIモードで、フロントエンドをVueかReactにしたバージョンの解説をしていただけると幸いです。