Help us understand the problem. What is going on with this article?

[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の中間テーブルになります。
いいね非同期.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

定期的に発信しています

twitter:https://twitter.com/yto_oct
note:https://note.com/yto_oty

yuto_1014
ご覧いただきありがとうござます!≪嗜好品メーカー営業マン→→WEBCAMP→→Javaエンジニア≫ [ twitter ] https://twitter.com/yto_oct [ note ] https://note.com/yto_oty
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした