LoginSignup
3
6

More than 3 years have passed since last update.

【Rails】いいね機能の実装

Last updated at Posted at 2020-06-06

始めに

忘れるといけないので、deviseと合わせたいいね機能の実装を簡単にまとめました。

前提 

環境
 Rails 5.2系
 Ruby 2.6系

使用ライブラリ
 devise
 Slim

実装

1.Railsアプリの作成

$ cd
$ rails new favorite_function
$ cd favorite_function

2.gem deviseとSlim導入

Gemfile

gem 'slim-rails'  #Slimのジェネレータを提供
gem 'html2slim'  #ERB形式のファイルをslim形式に変換してくれる
gem 'devise'
$ bundle

3.deviseをインストール

$ rails g devise:install
    create  config/initializers/devise.rb
    create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================

4.deviseインストールと同時に指示される4つの指示に従い編集を行う

 4-1. 1つ目

config/environments/development.rb

Rails.application.configure do
.
.
.
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
.
.
.
end

 4-2. 2つ目

$ rails g controller posts new index show
config/routes.rb

Rails.application.routes.draw do
  root to: 'posts#index'  #自分がアプリに設定しようと思っているroot_urlを指定
  resources :posts
  .
  .
  .
end

 4-3. 3つ目

app/views/layouts/application.html.erb

<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

 4-4. 4つ目

$ rails g devise:views

invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

5.ここで全てのビューをSlimに変換する

$ bundle exec erb2slim app/views/ --delete

 ※deviseのディレクトリに関してはSlimに変換するとdevise/shared/_error_messages.html.slimでエラーが起きるので修正する。

devise/shared/_error_messages.html.slim
- if resource.errors.any?
  #error_explanation
    h2

      #以下の3行でエラーが出るので以下のように修正
      = I18n.t("errors.messages.not_saved",
        count: resource.errors.count,
        resource: resource.class.model_name.human.downcase)

    ul
      - resource.errors.full_messages.each do |message|
        li
          = message

6.認証機構のビューを作成

app/views/layouts/application.html.slim

html
  head
  .
  .
  .
  body
    - if user_signed_in?
      =link_to 'ログアウト', destroy_user_session_path, method: :delete
    - else
      =link_to '新規登録', new_user_registration_path
      =link_to 'ログイン', new_user_session_path
  .
  .
  .

 ・deviseの、どのパスがどのアクションへ繋がっているかわからなくなったらrails routesコマンドで確認


7.モデルの作成

$ rails g devise User
$ rails g model Post content:string user:references 
$ rails g model Favorite user:references post:references
$ rails g migration add_columns_to_users username:string

$ rails db:migrate

 ・モデル名 : referencesとすることで、自動でマイグレーションファイル内でforeign_keyを張ってくれ、モデルファイル内で関連付けも行ってくれる。

 ・UserとPostは1対多の関係なので、Postに対してUserの所有であることを示すreferenceを張る。

 ・UserはPostに対する複数のいいねを持ちPostもUserからの複数のいいねを持つ多対多の関係なので、Favoriteに対してPostとUserの所有であることを示すreferenceを張る。

 ・deviseで作成されるデフォルトのUserモデルにはカラム追加できないので、カラム追加コマンドを実行しnameなどのUserに関するデータを作成。


8.モデルの関連付け

 8-1. Favoriteモデル

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

 8-2. Postモデル

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

 ・投稿に付いたいいねを取得する際にpost.favoritesという呼び出しが可能になる。post.favorites.countとすればいいね数を取得できる。

 8-3. Userモデル

user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :favorites
  has_many :favorite_posts, through: :favorites, source: :post
  has_many :posts
end

  ・ユーザーの付けたいいねを取得する際にuser.favoritesという呼び出しが可能になる。

  ・ユーザーがいいねを付けた投稿を自分のプロフィール画面に表示させたい場合がある。その際にuser.favorite_postsという呼び出しができるようになる。これはthroughオプションを使い、中間テーブルを通してPostモデルへと関連付けている。

  ・throughで使うモデルは、必ず先に関連付けを行っていなければならない。この場合だとfavoritesを先に関連付けていなければならない。

  ・sourceには、命名したモデル名の参照元となるモデル名を記述する。
has_many :好きなモデル名, through: :中間テーブル, source: :命名したモデルの参照元となるモデル名


9.コントローラの作成

$ rails g controller posts new index show  #deviseの設定の際に作成してる場合は省略
$ rails g controller users index show
$ rails g controller favorites create destroy

 ・(posts、)users、 favoritesコントローラ


10.ユーザー系機能の実装

 10-1. ルーティングの編集

config/routes.rb

Rails.application.routes.draw do

  resources :users, only: [:index, :show]  # ユーザー一覧やプロフィール画面用
  devise_for :users  # ログイン機構用

end

 10-2. usersコントローラの編集

app/controllers/users_controller.rb

class UsersController < ApplicationController

  before_action :authenticate_user!  # deviseが用意してくれてるuser認証

  def index
    @users = User.all
  end

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

 10-3. usersのビューの編集

  ● indexビュー
app/views/users/index.html.slim
h1 ユーザー一覧

- @users.each do |user|
  p
    = link_to user.email, user
  ● show
app/views/users/show.html.slim
h1 ユーザープロフィールページ

p
  = @user.username
  = @user.email

 10-4. applicationビューにユーザー系ページへのリンクを張る

app/views/layouts/application.html.slim
  body
    - if user_signed_in?
      .
      .
      .
   +  = link_to 'マイページ', user_path(current_user)
   +  = link_to 'ユーザー一覧', users_path
    - else
      .
      .

11.投稿系機能の実装

 11-1. ルーティングの編集

config/routes.rb
Rails.application.routes.draw do

  resources :users
  devise_for :users

  root to: 'posts#index'  # 投稿一覧をルートに指定
  resources :posts

end

 11-2. postsコントローラの編集

app/controllers/posts_controller.rb
class PostsController < ApplicationController

  def index
    @posts = Post.all
  end

  def new
    @post = Post.new
  end

  def create
    @post = current_user.posts.new(post_params)  # current_userはdeviseが用意してくれる、ログイン最中のユーザーを表す
    if @post.save
      redirect_to @post
    end
  end

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

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

   ・ 新規投稿される際、作成した投稿の所有者を投稿したユーザーにしたいのでcurrent_user.postsとする。これで、postsテーブルのuser_idにはcurrent_userのidが格納される。(UserモデルとPostモデルの関連付けを行っていないと、この方法は使えないので注意

 11-3. postsのビューの編集

  ● index
app/views/posts/index.html.slim
h1 投稿一覧

- @posts.each do |post|
  p= link_to post.user.email, user_path(post.user.id)
  p= link_to post.content

  

  ● new
app/views/posts/new.html.slim
h2 新規投稿

= form_with model: @post do |f|
  p
    = f.label :content
    = f.text_area :content
  = f.submit '投稿'

   

  ● show
app/views/posts/show.html.slim
h1 投稿詳細ページ

p= @post.content

 11-4. applicationビューに投稿系ページへのリンクを張る

app/views/layouts/application.html.slim
  body
    - if user_signed_in?
      .
      .
      .
      = link_to '投稿一覧', posts_path
    - else
      .
      .

12.いいね機能の実装

 12-1. 投稿にいいねボタンを作成

app/views/posts/index.html.slim
h1 投稿一覧

- @posts.each do |post|
  p= link_to post.user.email, user_path(post.user.id)
  p= link_to post.content

  - if user_signed_in?  # deviseが用意してくれてる、ユーザーがログインしてるかどうかを確かめるメソッド
    - if post.favorited_by?(current_user)
      p= link_to post.favorites.count, post_favorites_path(post.id), method: :delete
    - else
      p= link_to post.favorites.count, post_favorites_path(post.id), method: :post
  - else
    = post.favorites.count

   ・ログインしているユーザーだけが見れるようにするために、user_signed_in?を用いる。

   ・createアクションにていいねが付きdestroyアクションにていいねが消える仕組みなので、link_toヘルパーのmethodにそれぞれ明示的に指定してあげる。

   ・いいねをした場合はいいねの削除リンクを表示させ、いいねをまだしていない場合はいいねの作成リンクを表示させたいので、favorited_by(current_user)メソッドでそれを確認する。

   ・favorited_by(current_user)メソッドはこの後作成する。

   ・post.favorites.countでは、その投稿のいいね数を表示させている

 12-2. いいねの有無を確認するメソッドを定義

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


  + def favorited_by?(current_user)
  +   favorites.where(user_id: current_user.id).exists?
  + end
end

   ・Postモデルに対してだけ呼び出すメソッドのため、Postのインスタンスメソッドとして定義する。

   ・favoritesテーブルのuser_idにログイン中のユーザーのidが存在するかを確かめている。

   ・post.favorited_by(current_user)みたいな感じで使うことになる。少し簡単にして見てみる

posts/index.html.slim
h1 投稿一覧

- @posts.each do |post|
  .
  .
  .
  - if post.favorited_by?(current_user) 
      # いいねの削除リンクを表示
  - else
      # いいねの作成リンクを表示

   ・これは以下のコードを示している。

- if post.favorites.where(user_id: current_user.id).exists?
    # 削除リンク表示
- else
    # いいねの作成リンクを表示

   ・取得したpostのfavoritesテーブルからuser_idがログイン中のユーザーのidであるものを取得させて、それが存在するかどうかを確認させている。

   ・存在すればtrueを返す。いいねしてあるという意味なので、いいね削除リンクを表示させる。

   ・存在しなければfalseを返す。いいねしてないという意味なので、いいね作成リンクを表示させる。

 12-3. ルーティングの編集

config/routes.rb
Rails.application.routes.draw do

  resources :users
  devise_for :users

  root to: 'posts#index'
  resources :posts do
+   resource :favorites, only: [:create, :destroy]
  end

end

   ・favoritesはposts内にネストさせる。これによりpost_favorites_pathなどとして、投稿に対していいねを付けるためのパスの指定が簡単になる。 

 12-4. favoritesコントローラの編集

app/controllers/favorites_controller.rb
class FavoritesController < ApplicationController

  def create
    favorite = current_user.favorites.build(post_id: params[:post_id])
    favorite.save
    redirect_to posts_path
  end

  def destroy
    favorite = Favorite.find_by(post_id: params[:post_id], user_id: current_user.id)
    favorite.destroy
    redirect_to posts_path
  end
end

   ・ログイン中のユーザーのfavoritesテーブルにいいねを押した投稿のidを格納する。そして保存することでいいねの付与と見なさせている。

   ・いいね解除を押した投稿のidと、ログイン中のユーザーのidが一致するfavoritesテーブルのデータを取得して、消す。そうすることでいいね解除と見なさせている。

 12-5. ユーザーのページで、そのユーザーがいいねした投稿を表示する。ついでに、そのユーザーが投稿した投稿たちも表示させる。

  ● usersコントローラのshowアクションを編集
app/controllers/users_controller.rb
class UsersController < ApplicationController

  before_action :authenticate_user!

  def index
    @users = User.all
  end

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

    ・@postsでは、普通の投稿を表示するために通常の投稿を取得してる。

    ・@favorite_postsでは、ユーザーがいいねした投稿を取得してる。app/models/user.rbで、favoritesテーブルを通してfavorite_postsという名前でPostモデルと関連付けを行わせたのはこのため。

  ● show.html.slimを編集する
app/views/users/show.html.slim
h1 ユーザープロフィールページ

p
  = @user.username

h3 いいね
- @favorite_posts.each do |f_post|
  p
    = f_post.content

h3 投稿
- @posts.each do |post|
  p
    = post.content

    ・上はユーザーがいいねした投稿を表示する。

    ・下はユーザーが投稿した投稿たちを表示する。

    ・クリックイベントによる切り替えを行うとTwitterみたいな感じにできますが、ここではやりません。

以上で、いいね機能の実装は完了です。

大いに参考とした記事

 https://qiita.com/kazukimatsumoto/items/14bdff681ec5ddac26d1

3
6
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
3
6