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

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

#いいね機能の実装
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ファイルです。データベーステーブルは以下のように作成しました。

```ruby: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ファイルです。固有のツイートに対し、一人のユーザーのいいね！は一意です。

```ruby: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の関係が重複するので、以下のように記述している。 

```ruby: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にネストしています。

```ruby: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)を取得できません。


```html:_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>

```

```html:_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>
```

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

```html:_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アクションを作成しています。


```ruby: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
```

```ruby: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

```

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