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.
Rails 5.2.3Ruby 2.6.0The 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.
images table is outside the screen.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)

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

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

Well, the main subject is from here. We will realize these "Like" functions asynchronously.
remote: trueFirst, 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'
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'
.js.erb fileCreate .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.
It was a very easy step, but with the above implementation, I like it! Features can be implemented asynchronously: relaxed: It's surprisingly easy!
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