I will summarize how to implement the like function.
The implementation of the like function of synchronous communication was done by referring to the following book, and the subsequent asynchronous communication was done by referring to the following article. Thank you very much.
Revised 4th Edition Basic Ruby on Rails Basic Series
Asynchronous communication using js.erb (Implement the like function in 10 minutes)
This time, I will summarize how to implement the like function based on the photo posting application.
macOS Catalina 10.15.7 ruby 2.6.5 Rails 6.0.3.4
Only write the code where you need it. CSS code etc. are omitted.
Create an intermediate table (Vote model) called votes as a table to store the id of the user who liked it and the id of the photo who liked it.
% rails g model vote
db/migrate/□□□□□□□□□□□□□□_create_votes.rb
class CreateVotes < ActiveRecord::Migration[6.0]
def change
create_table :votes do |t|
t.references :photo, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
% rails db:migrate
Set the association between models as follows.
app/model/vote.rb
class Vote < ApplicationRecord
belongs_to :user
belongs_to :photo
(Omitted below)
app/models/user.rb
class User < ApplicationRecord
has_many :votes, dependent: :destroy
has_many :voted_photos, through: :votes, source: :photo
(Omitted below)
By using dependent:: destroy, when a user is deleted, the records in the votes table associated with that user are also deleted.
If you use has_many: photos, the relevance via votes will be confusing, so we use the name: voted_photos instead. After that,: voted_photos uses the source option to set it to be: photos in the: photo model (the description is confusing).
app/models/photo.rb
class User < ApplicationRecord
has_many :votes, dependent: :destroy
has_many :voters, through: :votes, source: :user
(Omitted below)
This also uses the name: votes instead of: user.
Create a method in the User model to determine if the user can like the button. Specifically, set the following rules. ① I can't like the photos I posted ② You can like one photo only once
app/models/user.rb
class User < ApplicationRecord
(abridgement)
def deletable_vote?(photo)
photo && photo.user != self && !votes.exists?(photo_id: photo.id)
end
(Omitted below)
True is returned if "photo exists", "photo user is not the logged-in user" and "logged user does not like this photo" are true.
If you check self on the console, the information of the logged-in user is stored.
In addition, create a method in the User model to determine if the user can like the Like button. True is returned if "photo exists", "photo user is not the logged-in user", and "logged-in user likes this photo" are true.
app/models/user.rb
class User < ApplicationRecord
(abridgement)
def votable_for?(photo) #← Judge if you can press a nice button
photo && photo.user != self && !votes.exists?(photo_id: photo.id)
end
def deletable_for?(photo) #← Determine if you can release the like button
photo && photo.user != self && votes.exists?(photo_id: photo.id)
end
(Omitted below)
app/models.vote.rb
class Vote < ApplicationRecord
belongs_to :user
belongs_to :photo
validate do
unless user && user.votable_for?(photo)
errors.add(:base, :invalid)
end
end
end
config/routes.rb
Rails.application.routes.draw do
(abridgement)
resources :photos do
(abridgement)
patch "like", "unlike", on: :member
get "voted", on: :collection
(Omitted below)
end
like action: Click the like button to save the record in the votes table unlike action: Clicking the Like button deletes the record from the votes table voted action: Show a list of photos you like (not explained this time)
Create a partial template for the Like button. Change the button to display based on the like rule created above ("Like" or "Unlike")
erb:app/views/shared/_votes.htmnl.erb
<div>
<% if current_user && current_user.votable_for?(photo) %>
<%= link_to "How nice" like_photo_path(photo), method: :patch %>
<% elsif current_user && current_user.deletable_for?(photo) %>
<%= link_to "Unlike", unlike_photo_path(photo), method: :patch %>
<% end %>
</div>
This time, on the photo details page, load the partial template for the like button with render. This will put a like button on the photo detail page. By setting photo: @photo, the instance variable @photo is added to the local variable photo in the partial template. @photo will then be created in the photos controller.
erb:app/views/photos/showl.html.erb
(abridgement)
<div>
<%= render partial: 'shared/votes', locals: { photo: @photo } %>
</div>
(Omitted below)
app/controllers/photos_controller.rb
class PhotosController < ApplicationController
before_action :set_photo, only: [(abridgement), :like, :unlike]
(abridgement)
#How nice
def like
current_user.voted_photos << @photo
redirect_to photo_path
end
#Like Delete
def unlike
current_user.voted_photos.destroy(@photo)
redirect_to photo_path
end
private
(abridgement)
def set_photo
@photo = Photo.find(params[:id])
end
(Omitted below)
current_user.voted_photos << @photo
By doing so, the user_id of current_user and the photo_id of @photo are saved in the votes table.
Also
current_user.voted_photos.destroy(@photo)
Will remove the user_id of current_user and the photo_id of @photo from the votes table.
** Neither affects the records in the users and photos tables. ** **
With the above, the like button for synchronous communication has been implemented.
↓ Behavior check (Every time you press the like button, you can see that the reload button on the upper left is X)
From here, the like function will be asynchronously communicated.
Modify the partial template for the like button.
erb:app/views/shared/_votes.htmnl.erb
<div>
<% if current_user && current_user.votable_for?(photo) %>
<%= link_to "How nice" like_photo_path(photo), method: :patch, remote: true %>
<% elsif current_user && current_user.deletable_for?(photo) %>
<%= link_to "Unlike", unlike_photo_path(photo), method: :patch, remote: true %>
<% end %>
</div>
Add remote: true to the link_to arguments of "Like" and "Unlike". Now you can implement the asynchronous communication function.
app/controllers/photos_controller.rb
(abridgement)
#How nice
def like
current_user.voted_photos << @photo
render 'vote.js.erb' #Added render method
# redirect_to photo_path, notice: "I liked it. ← Delete"
end
#Like Delete
def unlike
current_user.voted_photos.destroy(@photo)
render 'vote.js.erb' #Add render
# redirect_to photo_path, notice: "It has been deleted. ← Delete"
end
(Omitted below)
After processing the like or dislike actions, render'vote.js.erb' will execute the vote.js.erb file. vote.js.erb will be created later.
erb:app/views/photos/showl.html.erb
(abridgement)
<div id="vote-box"> #← Give id
<%= render partial: 'shared/votes', locals: { photo: @photo } %>
</div>
(Omitted below)
The js. In the erb
file, get this outer frame block based on the id of the outer frame block (div) of the partial template for the like button, and reload the partial template in the block. I gave the id "vote-box".
This time, we installed a like button on the photo details screen (only one post is displayed), so the id of the partial template outer frame is only one of "vote-box". If you want to install a like button on each photo on the photo list page, you need to give a different id to each outer frame block of each like button. In that case, it is necessary to devise such that a different id name is created for each outer frame block of each like button in the controller (for example, @ id_name = "vote-box-# {@ photo.id}" "Etc. I will omit the detailed explanation).
erb:app/views/photos/vote.js.erb
document.getElementById("vote-box").innerHTML = '<%= escape_javascript(render partial: 'shared/votes', locals: { photo: @photo }) %>'
This code reloads the partial template for the like button.
If you click the "Like" button, "Unlike" will be displayed. Also, if you click the "Like" button, "Like" will be displayed.
With the above, the like button for asynchronous communication has been implemented.
↓ Behavior check (Even if you press the like button, the reload button on the upper left does not change)
If you want to iconify the like button, you can use Font Awsome as follows.
erb:app/views/shared/_votes.htmnl.erb
<div>
<% if current_user && current_user.votable_for?(photo) %>
<%= link_to like_photo_path(photo), method: :patch, remote: true do %>
<i class="far fa-heart"></i>
<% end %>
<span><%= photo.votes.count %></span> #← The number of likes is also displayed
<% elsif current_user && current_user.deletable_vote?(photo) %>
<%= link_to unlike_photo_path(photo), method: :patch, remote: true do %>
<i class="fas fa-heart"></i>
<% end %>
<span><%= photo.votes.count %></span> #← The number of likes is also displayed
<% end %>
</div>
↓ I made the like button a heart symbol.
Please refer to the following videos for how to use Font Awsome. There is also a FontAwsome gem, but I couldn't install it well with the gem, so I copied and pasted Kit's Code from the FontAwsome homepage to application.html.erb and installed FontAwsome (see the video below).
[Ruby on Rails] Introducing Font Awesome to rails buttons Adding Font Awesome to Ruby on Rails [Font Awesome] Basic usage (introduction-animation) Font Awesome tutorial for beginners
** Please point out any mistakes or improvements. ** **
Recommended Posts