Implement the Like feature in Ajax with Rails.

What did you do

Carrying out Rails challenges. Among them, there was a task to create a "Like" function with Ajax. Actually, I used to create a "Like" function that is not asynchronous communication **.

▼ Click here for the record at that time Create a "Like" function in Rails-(1) Give an alias to the association Create a "Like" function with Rails-② Create action for "Like" Create a "Like" function in Rails-③ Enable to cancel "Like"

Also, in another article ** "Someday with Ajax" Like! While saying "I want to make a function" **, I didn't make it, so I would like to summarize the procedure for making it in a notebook.

In addition, the implementation procedure is introduced here as a memo. Please see the above article for the actual detailed implementation procedure.

The mounting environment is as follows.

Check and define specifications

The Like feature requires a slight twist in the definition of association. First of all, check the DB structure and specifications this time as well. As a result, the DB structure and specifications were as follows.

Image from Gyazo

Of these, ** "I can't like my post" ** is realized by not displaying the "Like" button in the view when posting my own post, as shown below. ..

▼ When posting your own (display the edit / delete button) Image from Gyazo

▼ When posting other than yourself (display the like button) Image from Gyazo

Also, ** like_posts ** written in red is another name for the association described below. Those who say "another name for the association ???" are explained in detail in this article.

Create a "Like" function in Rails-(1) Give an alias to the association

Creating a migration

Let's implement it from here. First, create a migration file. It is assumed that the users table and the posts table have been created respectively. Create the likes table as follows.

db/migrate/XXXXXXXXXX_create_likes.rb


class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.references :user
      t.references :post

      t.timestamps
      t.index [:user_id, :post_id], unique: true
    end
  end
end

Likes are achieved if you can properly enter the data in the likes table. (Please see this article for why this happens ^^) Create a "Like" function with Rails-② Create action for "Like"

Also,

t.index [:user_id, :post_id], unique: true

In the part of, the DB side controls so that "Like" to the same user and the same post cannot be posted.

Definition of association

The associations are described in the model files of posts, users, and likes as follows.

class User < ApplicationRecord
  # ★1
  has_many :posts, dependent: :destroy
  
  # ★2
  has_many :likes, dependent: :destroy
  has_many :like_posts, through: :likes, source: :post
end

class Post < ApplicationRecord
  # ★1
  belongs_to :user

  # ★2
  has_many :likes, dependent: :destroy
  has_many :users, through: :likes
end

class Like < ApplicationRecord
  belongs_to :post
  belongs_to :user
end

The two lines below ★ 1 and ★ 2 of User and Post are respectively.

-** ★ 1 ... Definition of post posted by user ** -** ★ 2 ... Definition of posts that users like **

is. The point is that the flow of User => Like => Post is given an alias of: like_posts and the association.

has_many :like_posts, through: :likes, source: :post

with this,

user = User.first
user.like_posts

↑ In this form, you can get a list of posts that the user has liked.

Controller definition

The contents of the controller are as follows. Define "Like" (create) and" Like "( destroy) as follows.

likes_controller.rb


class LikesController < ApplicationController
  def create
    @post = Post.find(params[:post])
    current_user.like(@post)
  end

  def destroy
    @post = Like.find(params[:id]).post
    current_user.unlike(@post)
  end
end

The like and unlike in the code are the User model methods that like and unlike, respectively.

User model method definitions (like and unlike)

In User.rb, define the following model method.

models/user.rb


class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  
  has_many :likes, dependent: :destroy
  has_many :like_posts, through: :likes, source: :post
  def own?(object)
    id == object.user_id
  end

  def like(post)
    likes.find_or_create_by(post: post)
  end

  def like?(post)
    like_posts.include?(post)
  end

  def unlike(post)
    like_posts.delete(post)
  end
end

like? Is a method to determine if ** the user has already liked the post **. I will post it because I will use it in the view after this.

Also, owm? Is not directly related to this implementation, but it is included because it will be used in the view after this. A method to determine the creator of the target object.

like_posts.delete(post)

I wonder if it's a miso to get a list of posts that users like with like_posts and destroy it.

View definition (for asynchronous "not" implementations)

After that, I will write the view. First, create a view with a ** non-asynchronous ** implementation. For readability, decoration elements and functions have been omitted.

- @posts.each do |post|
  - if logged_in?  #Login confirmation
    - if current_user.own?(post)  #Confirm ownership
      = link_to post_path(post), method: :delete do
        = icon 'far', 'trash-alt' #Trash can icon
      = link_to edit_post_path(post) do
        = icon 'far', 'edit' #Edit icon
    - else
      - if current_user&.like?(post) #Already like! Check if you are doing
        = link_to like_path(current_user.likes.find_by(post: post)), method: :delete do
        = icon 'fa', 'heart' #Heart (black)
      - else
        = link_to likes_path(post: post), method: :post do
          = icon 'far', 'heart' #Heart (white)

logged_in? is a Gem-generated method that determines if you are logged in.

In fact, with the implementation so far, the "Like" function itself has been created. When you press the "Like" button and reload the screen, you can see that "Like" and "Unlike" are switched respectively.

▼ If you press "Like" and then reload (press the button outside the screen), the icon will switch. Image from Gyazo

Asynchronously realize "Like!"

Well, the main subject is from here. We will realize these "Like" functions asynchronously.

Make the link remote: true

First, set the link to remote: true and make the communication asynchronous.

- @posts.each do |post|
  - if logged_in?
    - #abridgement
    - else #Link respectively_remote behind to:Add true
      - if current_user&.like?(post)
        = link_to like_path(current_user.likes.find_by(post: post)), method: :delete, remote: ture do
        = icon 'fa', 'heart'
      - else
        = link_to likes_path(post: post), method: :post, remote: ture do
          = icon 'far', 'heart'

Create a view file for Ajax-(1) Move button

Of the above view files, the like and unlike buttons are separated into separate partials for subsequent Ajax processing. In addition, the id attribute is also added so that it can be used as a marker for Ajax processing.

- @posts.each do |post|
  - if logged_in?
    - #abridgement
    - else id="like-button-#{post.id}" #Add id attribute
      - if current_user&.like?(post)
        = render 'likes/unlike_button', post: post #Move to partial
      - else
        = render 'likes/like_button', post: post #Same as above

Here is the contents of the partial.

slim:views/likes/_unlike_button.html.slim


= link_to like_path(current_user.likes.find_by(post: post)), method: :delete, remote: true do
  span.c-icon-button= icon 'fa', 'heart', class: 'fa-lg'

slim:views/likes/_like_button.html.slim


= link_to likes_path(post: post), method: :post, remote: true do
  span.c-icon-button= icon 'far', 'heart', class: 'fa-lg'

Create a view file for Ajax-(2) Create a .js.erb file

Create .js.erb files corresponding to the create and destroy actions of likes, respectively.

js:/views/likes/create.js.erb


$("#like-button-<%= @post.id %>").html("<%= j(render 'unlike_button', post: @post) %>")

js:/views/likes/destroy.js.erb


$("#like-button-<%= @post.id %>").html("<%= j(render 'like_button', post: @post) %>")

The .js.erb file is explained in this article, so please have a look if you like.

remote: true to POST ajax posts.

Complete!

It was a very easy step, but with the above implementation, I like it! Features can be implemented asynchronously: relaxed: It's surprisingly easy!

Image from Gyazo

Impressions ... It was the third time I made Ajax with both the "Like" function and the Rails function, so I'm glad I was able to do both: relaxed:

Recommended Posts

Implement the Like feature in Ajax with Rails.
Questions about implementing the Like feature (Ajax) in Rails
How to implement a like feature in Rails
I tried to implement Ajax processing of like function in Rails
Implement markdown in Rails
Implement user follow function in Rails (I use Ajax) ②
Implement user follow function in Rails (I use Ajax) ①
Implement application function in Rails
Japaneseize using i18n with Rails
Implement LTI authentication in Rails
Implement jCaptcha reload with ajax
Implement import process in Rails
Implement login function simply with name and password in Rails (3)
Use your own classes in the lib directory with Rails6
[Rails] Make pagination compatible with Ajax
About the symbol <%%> in Rails erb
Implement simple login function in Rails
Implement a like feature for posts
Place in the middle with css
Implement a contact form in Rails
First pagination feature added in rails
Implement CSV download function in Rails
Let's roughly implement the image preview function with Rails + refile + jQuery.
I tried to implement the image preview function with Rails / jQuery
How to implement search functionality in Rails
The identity of params [: id] in rails
Implement the algorithm in Ruby: Day 1 -Euclidean algorithm-
Prepare the format environment with "Rails" (VScode)
Rails refactoring story learned in the field
Fill the screen with buttons in TableLayout
[Rails 6] Interactive graph drawing with Ajax + chart.js
Implemented follow function in Rails (Ajax communication)
[Rails] How to get the user information currently logged in with devise
Implement the box ball system with Processing
How to compare only the time with Rails (from what time to what time, something like)
[Rails] Implement the product purchase function with a credit card registered with PAY.JP
Implement something like a stack in Java
How to implement ranking functionality in Rails
How to display the text entered in text_area in Rails with line breaks
[Rails] Reset the database in the production environment
[Rails] How to apply the CSS used in the main app with Administrate
Rails6: Extract the image in Action Text
Implement button transitions using link_to in Rails
Get the value of enum saved in DB by Rails with attribute_before_type_cast
[Rails / JavaScript / Ajax] I tried to create a like function in two ways.
[Rails] How to register multiple records in the intermediate table with many-to-many association
[Rails] How to operate the helper method used in the main application with Administrate
[Order method] Set the order of data in Rails
I rewrote the Rails tutorial test with RSpec
Use JDBC Manager with the settings in jdbc.dicon.
Implement Sign in with Twitter in spring-boot, security, social
Implement share button in Rails 6 without using Gem
I tried to organize the session in Rails
Publish the app made with ruby on rails
[Rails] Show multi-level categories in the breadcrumb trail
Implement the algorithm in Ruby: Day 3 -Binary search-
Implement star rating function using Raty in Rails6
One way to redirect_to with parameters in rails
Implement the algorithm in Ruby: Day 4-Linear search-
[Rails] How to easily implement numbers with pull-down
Implement iteration in View by rendering collection [Rails]