[RUBY] [Rails, JavaScript] Implemented like function (synchronous / asynchronous communication)

Overview

I will summarize how to implement the like function.

reference

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.

Book

Revised 4th Edition Basic Ruby on Rails Basic Series

article

[Rails] Like function full version! Synchronous likes, display of the number of likes, asynchronous likes, icon display, each implementation method is explained collectively

Asynchronous communication using js.erb (Implement the like function in 10 minutes)

Complete image

This time, I will summarize how to implement the like function based on the photo posting application.

465205b7ae2f8d84bb28a165cdf7a674.gif

Development environment

macOS Catalina 10.15.7 ruby 2.6.5 Rails 6.0.3.4

Implementation flow

1. Implementation of synchronous communication likes

  1. Creating an intermediate table model
  2. Model association
  3. Set the like rule to the method
  4. Set up routing
  5. Create a like button
  6. Set the like action on the controller

2. Asynchronous communication of like function

  1. Add remote: true to the argument of link_to
  2. Change the post-like action from redirect_to to render
  3. Give id to the outer frame block of the partial template for the like button
  4. js. Create erb file

This code

Only write the code where you need it. CSS code etc. are omitted.

1. Implementation of synchronous communication likes

1. Creating an intermediate table model

image.png

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

2. Model association

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.

3. Set the like rule to the method

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)

Set the validate method in the Vote model to prevent likes from being saved if the user does not meet the above rules.

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

4. Set up routing

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)

5. Create a like button

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)

6. Set the like action on the controller

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) 08e86be3b1268fad5ae9fa7ef3b91dcb.gif

2. Asynchronous communication of like function

From here, the like function will be asynchronously communicated.

1. Add remote: true to the argument of link_to

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.

2. Change the post-like action from redirect_to to render

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.

3. Give id to the outer frame block of the partial template for the like button

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).

4. js. Create erb file

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) 465205b7ae2f8d84bb28a165cdf7a674.gif

in conclusion

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. f354fe989d3f788243cf1394f68fb05d.gif

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

[Rails, JavaScript] Implemented like function (synchronous / asynchronous communication)
[Rails 6] Like function (synchronous → asynchronous) implementation
Posting function implemented by asynchronous communication in Rails
[Rails] Asynchronous implementation of like function
Implemented follow function in Rails (Ajax communication)
[Rails 6] Asynchronous (Ajax) follow function is implemented
[Rails] Implemented hashtag function
[Ruby on Rails] Asynchronous communication of posting function, ajax
[Rails] (Supplement) Implemented follow function
I tried to implement the like function by asynchronous communication
[Rails] I implemented the validation error message by asynchronous communication!
Implemented mail sending function with rails
[Rails] About implementation of like function
[Rails] [jQuery] Asynchronous like function implementation using remote: true and js.erb
JavaScript function
[Ruby on rails] Implementation of like function
[Rails / JavaScript / Ajax] I tried to create a like function in two ways.
[Rails 6] Ranking function
[Rails] Category function
Rails follow function
Implemented comment function
[Rails] Notification function
[For Rails beginners] Implemented multiple search function without Gem
Rails application guest login function implemented (devise not used)