[Rails]Ajaxを用いて非同期で投稿機能といいね機能の実装

## 実装すること
Ajaxを用いて非同期通信を行い、下記のコードを書いていきます。　
①新規の投稿が新着投稿一覧の一番前に画面のリロードなしで表示される
②いいねのハートの色といいね数を画面のリロードなしで変更する
※Ajaxについては下記リンク先で説明しています。
[Rails]Ajaxを用いて非同期でコメント機能の実装：https://qiita.com/yuto_1014/items/c7d6213139a48833e21a

## ER図
User:Item = 1:N
Item:Like = 1:N
User:Like = 1:N
LikeテーブルがItemとUserの中間テーブルになります。
<img width="646" alt="いいね非同期.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539971/1b0f9721-e574-5dc9-b023-6e8b7568c9cf.png">

## ページ設計
・[items/index]投稿一覧でいいねができる
・[items/show]投稿詳細でいいねができる
## モデルの作成
今回はUserモデル、Itemモデル、Likeモデルを作成します。
Userモデルはdeviseを使用してモデルを作成していきます。
### devise・jQuery・refile,refile-mini_magickの導入・Userモデルの作成
```Gemfile.
gem 'devise'
gem 'jquery-rails'
gem "refile", require: "refile/rails", github: 'manfe/refile'
gem "refile-mini_magick"
```
```
$ bundle install
$ rails g devise:install
$ rails g devise User name:string
$ rails g devise:views
```
### Itemモデル・Likeモデルの作成
```
$ rails g model Item title:string image_id:string user_id:integer
$ rails g model Like item_id:integer user_id:integer
$ rails db:migrate
```
## アソシエーションの確認
### Userモデル
userはそれぞれたくさんのitemを投稿をすることができ、投稿にいいねができるというイメージです。
`dependent: :destroy`は、itemがuserに依存していることから、userが消えればitemも消えるようにするためです。

```app/models/user.rb
class User < ApplicationRecord
 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable
 
 has_many :items, dependent: :destroy
 has_many :likes, dependent: :destroy

 #既にいいねしているかどうか
 def already_liked?(item)
   self.likes.exists?(item_id: item.id)
 end
end
```
### Itemモデル

```app/models/item.rb
class Item < ApplicationRecord
 belongs_to :user
 has_many :likes, dependent: :destroy
 #refile
 attachment :image
end
```
### Likeモデル
`validates_uniqueness_of`によって、item_idとuser_idの組が1組しかないようにバリデーションをかけます。

```app/models/like.rb
class Like < ApplicationRecord
 belongs_to :item
 belongs_to :user

 validates_uniqueness_of :item_id, scope: :user_id
end
```
## コントローラーの作成
```
$ rails g controller users
$ rails g controller items index show
$ rails g controller likes
```

## ルーティングの作成
投稿一覧、投稿詳細、投稿作成、投稿へのいいね、いいねの取り消しができるようにしていきます。
いいねがどの投稿へのものであるかを識別するために、ルーティングのURLに投稿のIDを含めます。(ネストする)
具体的には「/item/10/like/」といったURLになります。10がitem_idです。

```config/routes.rb
Rails.application.routes.draw do
  devise_for :users

  root 'items#index'

  resources :users
  resources :items, only: [:index, :show, :new, :create] do
    resource :likes, only: [:create, :destroy]
  end

end
```
## コントローラーとビューの編集(新規投稿・投稿一覧・投稿詳細)

[新規投稿]非同期で投稿一覧の新着順の一番最新に表示されるようにする
[投稿一覧]新着順で8投稿だけ表示されるようにする
### items_controller.rb

```app/controllers/items_controller.rb
class ItemsController < ApplicationController

   def new
        @item = Item.new
   end

   def create
		@item = Item.new(item_params)
	      if @item.save
               @items = Item.order(created_at: :desc).limit(8)
               render :create
	      else
               render :new
	      end
    	end
	end

   def index
		#新着順
		@items = Item.order(created_at: :desc).limit(8)
	end

   def show
        @item = Item.find(params[:id])
    end

    private
  	def item_params
    	params.require(:item).permit(:title, :user_id)
  	end
end
```
### items/index.html.erb
投稿部分はパーシャルにします。

```app/views/items/index.html
  <div id="item_index_new">
    <%= render 'item_new', items: @items %>
  </div>
```


### items/_item_new.html.erb
いいね部分はパーシャルにします。
`id="index_like_<%= item.id %>"` この部分で投稿それぞれに一位な値を付与しています。

```app/views/items/_item_new.html

<h2>新着投稿</h2>
     <% items.each do |item| %>
	    <%= link_to item_path(item) do %>
		    <%= attachment_image_tag item, :image, size: "190x190", class: "coffee_image_media" %>
	    <% end %>
	    <%= link_to user_path(item.user_id) do %>
		    <%= attachment_image_tag item.user, :profile_image, fallback: "no_image.jpg", class:"profile-img-circle", size: "30x30" %>
		    <%= item.user.name %>
	    <% end %>
	    <div id="index_like_<%= item.id %>">
		    <%= render 'likes/like', item: item %>
	    </div>
     <% end %>
```
### items/show.html.erb
いいね部分はパーシャルにします。
`id="show_like_<%= item.id %>"` この部分で投稿それぞれに一位な値を付与しています。


```app/views/items/show.html

<h1>投稿詳細ページ</h1>
<%= attachment_image_tag @item.user, :profile_image, fallback: "no_image.jpg", class:"profile-img-circle", size: "100x100" %>
<%= @item.user.name %>

<div id="show_like_<%= @item.id %>">
  <%= render 'likes/like', item: @item %>
</div>
```

### items/new.html.erb
`hidden_field`でuser_idにcurrent_user.idを代入しています。

```app/views/items/new.html

<%= form_for(@item, url: items_path, remote: true) do |f| %>
  <p>画像投稿</p>
		<%= f.attachment_field :image %>
  <p>タイトル</p>
		<%= f.text_field :name, class: "form-control" %
        <%= f.hidden_field :user_id, :value => current_user.id %>
        <%= f.submit "保存", class:"form-control" %>
<% end %>
```

### items/create.js.erb
`id=item_index_new`の一番前に追加されるように、内容を差し替えます。
items_controller.rbのcreateアクションが呼ばれた時は、views/items/create.js.erbが呼ばれます。他のコントローラの時も同様で、likes_controller.rbのindexアクションが呼ばれた時は、views/items/index.js.erbが呼ばれます。
※ファイルがあれば呼び出されます。

```app/views/items/create.js
$("#item_index_new").html("<%= escape_javascript(render 'items/item_new', items: @items) %>")
```
## コントローラとビューの編集(いいね)
### likes_controller.rb

```app/controllers/likes_controller.rb
class LikesController < ApplicationController

  before_action :item_params
  def create
      like = current_user.likes.new(item_id: @item.id)
      like.save
  end

  def destroy
    @like = Like.find_by(user_id: current_user.id, item_id: @item.id).destroy
  end

  private
  def item_params
    @item = Item.find(params[:item_id])
  end

end
```

### likes/_like.html.erb
・今回のいいね処理ではすでにあるパーシャルを切り替えるだけなので、何も返さないが、表示は切り替えます。切り替えるのにjsを使います。
・`if user_signed_in?`で、もしユーザーがログインしていなければ、ハートが表示はされるがいいねはできないようにしています。
・`already_liked?`で、Userモデルのメソッドを呼び出し、既にいいねしているかどうかを確認しています。
・`remote: true`で、リンクを非同期化しています。
・`item.likes.count`で、いいね数を取っています。
・`fa fa-heart`は、いいねの表示にfontawesomeを使用しています。

```app/views/likes/_like.html
<% if user_signed_in? %>
	<% if current_user.already_liked?(item) %>
		<%= link_to item_likes_path(item), method: :delete, remote: true do %>
			<i class="fa fa-heart" aria-hidden="true" style="color: red;">
				<%= item.likes.count %>
			</i>
		<% end %>
	<% else %>
		<%= link_to users_item_likes_path(item), method: :post, remote: true do %>
			<i class="fa fa-heart" aria-hidden="true" style="color: #C0C0C0;">
				<%= item.likes.count %>
			</i>
		<% end %>
	<% end %>
<% else %>
	<i class="fa fa-heart" aria-hidden="true">
			<%= item.likes.count %>
	</i>
<% end %>
```


### likes/create.js.erb
いいねをする

```app/views/likes/create.js

//投稿一覧いいね差し替え
$("#index_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
//投稿詳細いいね差し替え
$("#show_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
```

### likes/destroy.js.erb
いいねを取り消す

```app/views/likes/destroy.js
//投稿一覧いいね差し替え
$("#index_like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
//投稿詳細いいね差し替え
$("#show/like_<%= @item.id %>").html("<%= j(render partial: 'likes/like', locals: { item: @item}) %>");
```

## 最後に
最後までご覧いただきありがとうございます。
初学者ですので間違っていたり、分かりづらい部分もあるかと思います。
何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。
Ajax関連でコメントとフォローの非同期も実装しています。
↓
[Rails]Ajaxを用いて非同期でコメント機能の実装
https://qiita.com/yuto_1014/items/c7d6213139a48833e21a
[Rails]Ajaxを用いて非同期でフォロー機能の実装
https://qiita.com/yuto_1014/items/8d508b84fd0c2316ba01

## 参考
Railsでいいね機能を実装しよう
https://qiita.com/nojinoji/items/2c66499848d882c31ffa