[Java] [Rails] Asynchronous implementation of like function

8 minute read

1.First of all

Like the demo video below, we will implement a function that allows you to like the content posted by the user. Image from Gyazo

2. Prerequisites

Assuming that the user registration function and posting function have already been implemented, add “like function” to them. I will proceed in that flow.

It will be easier to understand if you can imagine the following database structure. ER diagram (Qiita).png

3. Like feature implementation

** ■ Flow of implementation **

Roughly explained, it will be implemented according to the following flow. ・Model creation ↓ · Add routing ↓ ・Creating a controller ↓ · Creating views

Then let’s go fast!

3-1. Creating Like model

First, create a Like model. Execute the following command in the terminal.

terminal


$ rails g model Like

A new migration file will be created, so edit it as follows.

class CreateLikes <ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
  # === Addendum ===
      t.references :tweet, foreign_key: true, null: false
      t.references :user, foreign_key: true, null: false
  # === Addendum ===
      t.timestamps
    end
  end
end

If you save it as references type as above, you can specify tweet_id and ```user_id

as foreign keys.


Now run the migration file to create the likes table.


#### **` terminal`**

$ rails db:migrate

After running the above command, check if the likes table has been created.
If you can confirm that it has been created successfully, the next step is to set up the association.

## 3-2. Association settings
An association is an association between two models.
User model and Like model, Tweet model and Like model, and their respective associations will be set.

### Setting the association of User and Like models

First, set up the association between the User model and the Like model.

The relationship between the two models is as follows.
・User can have multiple likes
・There is only one user who liked A

In other words, the User model and the Like model have a "one-to-many" relationship.
Now let's actually write the code.

Add the following code to the User model.

```ruby:app>models>user.rb
class User <ApplicationRecord

  has_many :tweets, dependent: :destroy

  # Add this line
  has_many :likes

end

has_many indicates that there is a “one-to-many” relationship with other models.

Next, add the following code to the Like model.

class Like <ApplicationRecord

  # Add this line
  belongs_to :user

end

belongs_to is the opposite of has_many, indicating a “many-to-one” relationship with other models.

With this, the association settings for the User model and Like model are completed.

Setting the association between Tweet and Like models

Follow the same procedure to set the association between Tweet and Like models.

The relationship between the two models is as follows. ・Multiple likes for one post ・Like A has only one post associated with it

In other words, the Tweet model and the Like model also have a one-to-many relationship. Now let’s actually write the code.

Please add the following code to the Tweet model.

class Tweet <ApplicationRecord
  belongs_to :user

  # Add this line
  has_many :likes, dependent: :destroy

end

By adding ```dependent: :destroy

, when a post is deleted, the likes associated with that post are also deleted.



Next is the Like model.

```ruby:app>models>like.rb
class Like <ApplicationRecord

  belongs_to :user

  # Add this line
  belongs_to :tweet

end

This completes the association settings for all models.

3-3. Validation settings

Since we want the number of times that one user can like the post A once, we set the validation so that it cannot be pressed more than once.

Please add as follows to the Like model.

class Like <ApplicationRecord

  belongs_to :user
  belongs_to :tweet

  # Add this line
  validates :user_id, uniqueness: {scope: :tweet_id}

end

By writing as above, it is possible to prevent user_id and ```tweet_id

from overlapping.


This completes the validation settings.


## 3-4. Add routing
Finally, we will implement the nice features in earnest.

First, let's add a route to use with the Like function.
Please add the code as follows.

```ruby:config>routes.rb
Rails.application.routes.draw do
  devise_for :users,
    controllers: {registrations:'registrations'}

  resources :tweets, only: [:index, :new, :create, :show, :destroy] do

# Add this line
    resources :likes, only: [:create, :destroy]

  end

end

Since we need to add routes for saving and deleting likes, we define the create and ```destroy

actions of the likes controller.



By nesting routes, you can clearly indicate which post is associated with a like.

After adding the code, be sure to check that the routing settings are correct with the ```rails routes
#### **` command.`**

3-5. likes Creating controller

Next, we will create the likes controller. Execute the following command in the terminal.

terminal


$ rails g controller likes

You can create a likes controller by executing the above command.

Now let’s create the create and ```destroy

actions in the created likes controller.


Please add the code as follows.

```ruby:app>controllers>likes_controller.rb
class LikesController <ApplicationController

  # === Addendum ===
  def create
    @like = current_user.likes.build(like_params)
    @tweet = @like.tweet
    if @like.save
      respond_to :js
    end
  end

  def destroy
    @like = Like.find_by(id: params[:id])
    @tweet = @like.tweet
    if @like.destroy
      respond_to :js
    end
  end

  private
    def like_params
      params.permit(:tweet_id)
    end
  # === Addendum ===

end

Assuming you understand private methods and params, I will briefly explain the added code.

create action

First, @like contains the user_id of the user who liked the post and the tweet_id information of the post that was “liked”. I will. This code uses the build method to create an instance.

Next, @tweet contains the information of the post associated with ```@like

, that is, the information of the post that was liked.


The ```@tweet
#### **` is used in the view creation to determine which post has liked the post.`**

In the last if @like.save part, the format of the response returned when “Like” is pressed is switched by the ```respond_to

method.


In order to reflect the view in real time when "Like" is pressed, the response is returned in the JS format.

### destroy action
Since there are many overlaps with the contents explained in the create action, I will briefly explain it.The ```id``` is determined from the received HTTP request and the record specified in ```@like
#### **` I am entering information.`**

Again, in order to reflect the view in real time, the response is returned in the JS format.

3-5. Creating a view

It is finally time to create the view.

First of all, let’s say that we will edit the view screen of the post list, but first define the method to use in the view.Please add the following code to the Tweet model.

class Tweet <ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy

  #Additional part (liked_by method)
  def liked_by(user)
    Like.find_by(user_id: user.id, tweet_id: id)
  end
  #Additional parts

end

The liked_by method added above searches for a like match between user_id and ```tweet_id

and returns nill if there is none.



Then, add the following code to ```app/views/tweets/index.html.erb
#### **`.`**

<% @tweets.each do |tweet| %>

  # Like button added to the part you want to display
  <div class="content-like">
    <ul class="content-like__icons">
      <li id="<%= tweet.id.to_s %>">
        <% if tweet.liked_by(current_user).present? %>
          <%= link_to (tweet_like_path(tweet.id, tweet.liked_by(current_user)), method: :DELETE, remote: true, class: "liked") do %>
            <i class="far fa-thumbs-up"></i>
          <% end %>
        <% else %>
          <%= link_to (tweet_likes_path(tweet), method: :POST, remote: true, class: "like") do %>
            <i class="far fa-thumbs-up"></i>
          <% end %>
        <% end %>
      </li>
    </ul>
  </div>
  #End here

<% end %>

By passing current_user as an argument to ```liked_by

, it is determined whether or not the currently logged-in user has liked the post.



Now, when the user doesn't like, click the "Like button" to execute the ```create``` action created earlier, and when the user likes it, `` I could execute the `destroy
#### **` action and make a conditional branch.`**

Don’t forget to add the remote: true option to link_to as you need to call the

.

js.erb
#### **` file when the link is pressed. Please give me.`**

For the “Like button” icon, Font Awesome is used. For the introduction method, I think the following qiita articles etc. will be helpful. How to install rails font-awesome-sass

Next, create a file that will be output when the create action is executed. Create a likes folder directly under the app/views folder, and create a ```create.js.erb

file in it.



After creating the file, add the code as follows.

```ruby:app>views>likes>create.js.erb

$('#<%= @tweet.id.to_s %>').
  html('<%= j render "tweets/liked", {tweet: @tweet} %>');

In the above code, when the create action is executed, the _liked.html.erb file in the ```tweets

folder is called.



Create a ```_liked.html.erb``` file in the ```tweets
#### **` folder and add the following code.`**
<%= link_to (tweet_like_path(tweet.id, tweet.liked_by(current_user)), method: :DELETE, remote: true, class: "liked") do %>
  <i class="far fa-thumbs-up"></i>
<% end %>

In the above code, when I press the “Like button”, the HTML that cancels the “Like” is displayed.

In the same flow, let’s create a file that will be called when the destroy action is executed. Create a destroy.js.erb file in the ```app/views>likes

folder.



After creating the file, add the code as follows.

```ruby:app>views>likes>destroy.js.erb

$('#<%= @tweet.id.to_s %>').
  html('<%= j render "tweets/like", {tweet: @tweet} %>');

Create a _like.html.erb file in the ```tweets

folder and add the following code.



```html:app>views>tweets>_like.html.erb
<%= link_to (tweet_likes_path(tweet), method: :POST, remote: true, class: "like") do %>
  <i class="far fa-thumbs-up"></i>
<% end %>

With the above, we were able to implement a nice feature asynchronously.

After that, it looks like, but when the class name is like liked when it is liked, it is ```like

, so try customizing it with CSS. Please give me.



In the case of me, I changed the color of the icon to red when liked and gray when it was not.

```css:app>assets>stylesheets>tweets>_tweet.css
.like {
  color: gray;
}

.liked {
  color: red;
}

4. Finally

This is my first post, but writing an article is more difficult than I had imagined. If there are parts that are difficult to understand or are incorrect, I would be grateful if you could point me out.

Thank you for reading to the end ☺️