3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

タイムラインでいいね機能をつけたい!!~前編~

Last updated at Posted at 2018-04-27

#前説
いいね機能の実装コードの全貌を載せています。少し、情報量が多くて大変です。コードのわかりにくいところに補足をつけておきました。何か間違いやご指摘などございましたら、ご連絡ください。

#いいね機能の実装
Twitterでタイムラインのツイートに対していいね!をしますよね!
あの機能を一応実装しました。非同期通信で行う予定でしたが、今回はRails部のみの実装を行いました。

#前提
MVCモデルをある程度理解してらっしゃる方向け
Ruby言語をある程度触ってらっしゃる方向け
Rails 4.2です。

#環境整備
Likesのコントローラー、モデル、データベーステーブルを用意します。

bundle exec rake db:create
bundle exec rails g model like
bundle exec rails g controller likes

#データベース設定

以下はmigrateファイルです。データベーステーブルは以下のように作成しました。

20170431082734_likes.rb
class Likes < ActiveRecord::Migration
  def change
    create_table :likes do |t|
      t.integer :user_id, null: false
      t.integer :product_id, null: false

      t.timestamps

      t.index :user_id
      t.index :product_id
      t.index [:user_id, :product_id] , unique: true
    end
  end
end

bundle exec rake db:migrate

#Model
Modelファイルです。固有のツイートに対し、一人のユーザーのいいね!は一意です。

like.rb
class Like < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
  validates :user_id, presence: true
  validates :product_id, presence: true
  # validates_uniqueness_of :product_id, scope: :user_idでも可能
end

product.rbのところでは、データベースのテーブルのアソシエーションの関係性が問題です。
一人のユーザーがいくつものツイート(products)を投稿。→ user:products = 1 : 多
一つのツイート(product)に対し、複数人のいいね。→ (users:likes):product = 多 : 1
userとproductの関係が重複するので、以下のように記述している。

product.rb
class Product < ActiveRecord::Base
  belongs_to :user
  has_many :likes , dependent: :destroy #削除されると自動的にいいねは消える
  has_many :like_users, through: :likes, source: :user #多ー多関係であるので区別化してる

  def user_exist?(user)
    like_users.include?(user)
    # いいねしたユーザーのなかにuser(引数)がいるかどうか
  end
end

#ルーティング
ルーティングは以下の通りです。productにネストしています。

route.rb
resources :products do
  resources :likes, only: [:create, :destroy]
end
product_likes POST   /products/:product_id/likes(.:format)        likes#create
product_like DELETE /products/:product_id/likes/:id(.:format)    likes#destroy

#View
Viewファイルでは以下のように編集します。
index.html(タイムライン)やshow.html(Tweet詳細ページ)、show.html(User詳細ページ)から参照できるようにします。それぞれのページからrenderで参照させなくてはいけませんが、index.html(タイムライン)アクセス時ではproduct_id(Tweetのid)を取得できません。

_index_to_product.html.erb
<div class = "product">
<% products.each do |a_product| %>
  <div class="card col s3">
    <%= render 'products/product' , a_product: a_product %>
    </ul>
  </div>
<% end %>
</div>

_product.html.erb
<ul class="collection">
・  ・  ・
 <li id="product-<%= a_product.id %>" class="collection-item" >
    <span class="timestamp">
    </span>
    <%= render 'products/likes' , a_product: a_product %>
 </li>
・  ・  ・
</ul>

正直ここが一番苦戦しました。

_like.html.erb
  <% if current_user %>
    <% if a_product.user_exist?(current_user) %>
      <!--現在のユーザーがすでにいいね!を押してるかどうかの判定-->
      <%= form_for [a_product,@like],url: product_like_path(a_product.id,@like.id),method: :delete do |f| %>
      <!--すでにいいね!を押していると、deleteメソッドで削除-->
      <!--ここのform_forの引数がわかりにくいですが、ルーティングのDELETEのパスの引数に:product_idと:id(like)を当てはめています。-->
       <% button_tag(type: "submit",class: "waves-effect waves-light btn") do %>
        <i class="fa fa-arrow-left"></i>
          <%= f.submit "いいね解除" %>
         <% end %>
       <% end %>

    <% else %>
      <div class = "action">
      <%= form_for [a_product, @likes], methos: :post do |f| %>
          <%= button_tag type: "submit", class: "waves-effect waves-light btn" do %>
                    <!--いいね!を押していないと、postメソッドで追加-->
            <i class="fa fa-arrow-left"></i>
            <%= f.submit "いいね" %>
           <% end %>
          <% end %>
      </div>
    <% end %>
  <% end %>

#Controller
次はコントローラーです。いいね!をデータベースに保存するcreateアクションとdestroyアクションを作成しています。

product_contorller.rb
  def index
    @likes = Like.new(user_id: current_user.id, product_id: params[:product_id])
    #create時に生成するインスタンス
    @like = Like.find_by(user_id: current_user.id)
    #destroy時にテーブルから探し出す
    @products = Product.order("created_at DESC").page(params[:page]).per(10)
  end
like_contorller.rb
class LikesController < ApplicationController
  def create
    Like.create(like_params)
    redirect_to controller: :products, action: :index
      # データベースにuserのidを含んだ情報を保存
      # current_userの情報を引数に入れて、Likeしたuserとしてデータベースに保存
  end

  def destroy
    @like_product = Like.find(params[:id]).product
    # いいねされたツイートのidを取得してのそツイートの情報を取得
    Like.find_by(user_id: current_user.id, product_id: params[:product_id]).destroy
      # すでにLike!されているツイートのidを取得してその現在のユーザーがしたいいね!を削除
    redirect_to controller: :products, action: :index
  end

  private
  def like_params
    params.permit(:product_id).merge(user_id: current_user.id)
  end
end

これで一応、いいね機能は実装できました。あとは非同期通信で画面遷移なしでいいね!できるようにします。
続きはまた次回!

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?