LoginSignup
23
42

More than 1 year has passed since last update.

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

Last updated at Posted at 2020-02-08

実装すること

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

23
42
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
23
42