実装すること
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の中間テーブルになります。
ページ設計
・[items/index]投稿一覧でいいねができる
・[items/show]投稿詳細でいいねができる
モデルの作成
今回はUserモデル、Itemモデル、Likeモデルを作成します。
Userモデルはdeviseを使用してモデルを作成していきます。
devise・jQuery・refile,refile-mini_magickの導入・Userモデルの作成
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も消えるようにするためです。
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モデル
class Item < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy
#refile
attachment :image
end
Likeモデル
validates_uniqueness_of
によって、item_idとuser_idの組が1組しかないようにバリデーションをかけます。
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です。
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
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
投稿部分はパーシャルにします。
<div id="item_index_new">
<%= render 'item_new', items: @items %>
</div>
items/_item_new.html.erb
いいね部分はパーシャルにします。
id="index_like_<%= item.id %>"
この部分で投稿それぞれに一位な値を付与しています。
<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 %>"
この部分で投稿それぞれに一位な値を付与しています。
<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を代入しています。
<%= 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が呼ばれます。
※ファイルがあれば呼び出されます。
$("#item_index_new").html("<%= escape_javascript(render 'items/item_new', items: @items) %>")
コントローラとビューの編集(いいね)
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を使用しています。
<% 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
いいねをする
//投稿一覧いいね差し替え
$("#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
いいねを取り消す
//投稿一覧いいね差し替え
$("#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