Featured image of post Twitor #3: Like Features

Twitor #3: Like Features

Let's create Twito!, a Tweet Clone App by using Ruby on Rails !

Intro

In this chapter we are going to connected user and post database, also creating like feature.

Modifying Database

Add user_id to Post

To start, let’s create a migration file with the filename “add_user_id_to_posts” using the rails g migration command. We’ll modify the content of the migration file as shown below

1
2
3
4
5
class AddUserIdToPosts
  def change
    add_column :posts, :user_id, :integer
  end
end

then run rails db:migrate

Add likes table

Let’s create the Like model and a migration file using the command

  • rails g model Like user_id:integer post_id:integer

This table will have two columns, user_id and post_id. After migration has been created, run rails db:migrate to apply changes to the database.

Add validation

Post Model

Each post shall have a creater, so in file of models/post.rb

1
2
3
4
class Post < ApplicationRecord
  ...
  validates :user_id, {presence: true}
end

Like Model

Sine the “like” data is incomplete unless both user_id and post_id exist, let’s add the validtion presence: true for both user_id and post_id.

1
2
3
4
class Like < ApplicationRecord
  validates :user_id, {presence: true}
  validates :post_id, {presence: true}
end

Add routes

For like and dislike action, we need to create new route to handle this. We also need to create new route for viewing user’s like in the user detail page

1
2
3
4
  post "likes/:post_id/create" => "likes#create"
  post "likes/:post_id/destroy" => "likes#destroy"

  get "users/:id/likes" => "users#likes"

Modifying Controller

Add before_action on Post

We’ll limit access to the edit, update, and destroy actions. So we define the ensure_correct_user method in the Post controller,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...
  # Define the ensure_correct_user method
  def ensure_correct_user
    @post = Post.find_by(id: params[:id])
    if @current_user.id != @post.user_id
      flash[:notice] = "Unauthorized access"
      redirect_to("/posts/index")
    end
  end
...

and use this method

1
2
3
4
5
class PostsController < ApplicationController
  before_action :authenticate_user
  # Set the ensure_correct_user method as a before_action
  before_action :ensure_correct_user, {only: [:edit, :update, :destroy]}
...

Display user name and image in Post

To do this, define @user in method show in Post Controller. See previous steps here

1
2
3
4
  def show
    @post = Post.find_by(id: params[:id])
    @user = @post.user
  end

Noted that we can use method .user on @post instance since we declare code below in the post model.

1
2
3
4
5
6
7
8
9
class Post < ApplicationRecord
  validates :content, {presence: true, length: {maximum: 140}}
  validates :user_id, {presence: true}
  
  def user
    return User.find_by(id: self.user_id)
  end
  
end

Save user_id to Post

In file posts_controller.rb, put

1
2
3
4
5
6
def create
  @post = Post.new(
    content: params[:content],
    user_id: @current_user.id
  )
end

Add like action on User

We will create user’s like on the user detail /users/show.html.erb, then we need to prepare data that we want to pass to template from database by creating new method in users_controller.rb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...
  def likes
    # Define the @user variable
    @user = User.find_by(id: params[:id])
    
    # Define the @likes variable
    @likes = Like.where(user_id: @user.id)
    
  end
...

Set LikesController

We want to users can like post undo their like so we create new controller to handle likes table and should put create and destroy action in this controller.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class LikesController < ApplicationController
  before_action :authenticate_user

  def create
    @like = Like.new(user_id: @current_user.id, post_id: params[:post_id])
    @like.save
    redirect_to("/posts/#{params[:post_id]}")
  end

  def destroy
    @like = Like.find_by(user_id: @current_user.id, post_id: params[:post_id])
    @like.destroy
    redirect_to("/posts/#{params[:post_id]}")
  end
end

Modifying Views

Post Views

Importing font awesome

Add font-awesome to head tag in views/layouts/application.html.erb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!DOCTYPE html>
<html>
  <head>
    <title>TweetApp</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
  </head>
  ...

Displaying User on Post Index

To add user data on post index, we should modify /app/views/posts/index.html.erb

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<div class="main posts-index">
  <div class="container">
    <% @posts.each do |post| %>
      <div class="posts-index-item">
        <div class="post-left">
          <img src="<%= "/user_images/#{post.user.image_name}" %>">
        </div>
        <div class="post-right">
          <div class="post-user-name">
            <%= link_to(post.user.name, "/users/#{post.user.id}") %>
          </div>
          <%= link_to(post.content, "/posts/#{post.id}") %>
        </div>
      </div>
    <% end %>
  </div>
</div>

in line 6, post instance using user method since we already declare instance method here.

Displaying likes on Post Detail

We will put like information in post detail posts/show.html.erb by writing a condition such <% if Like.find_by(user_id: @current_user.id, post_id: @post.id) %>and write likes count <%= @likes_count %>.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div class="main posts-show">
  <div class="container">
    <div class="posts-show-item">
      <div class="post-user-name">
        <img src="<%= "/user_images/#{@user.image_name}" %>">
        <%= link_to(@user.name, "/users/#{@user.id}") %>
      </div>
      <p>
        <%= @post.content %>
      </p>
      <div class="post-time">
        <%= @post.created_at %>
      </div>
      <% if Like.find_by(user_id: @current_user.id, post_id: @post.id) %>
        <%= link_to("/likes/#{@post.id}/destroy", {method: "post"}) do %>
          <span class="fa fa-heart like-btn-unlike"></span>
        <% end %>
      <% else %>
        <%= link_to("/likes/#{@post.id}/create", {method: "post"}) do %>
          <span class="fa fa-heart like-btn"></span>
        <% end %>
      <% end %>
      <%= @likes_count %>
      <% if @post.user_id == @current_user.id %>
        <div class="post-menus">
          <%= link_to("Edit", "/posts/#{@post.id}/edit") %>
          <%= link_to("Delete", "/posts/#{@post.id}/destroy", {method: "post"}) %>
        </div>
      <% end %>
    </div>
  </div>
</div>

To use the link_to method with an HTML element, you need to use a slightly different syntax. Like the code below, you can write an HTML element between <%= link_to(URL) do %> and <% end %>, and make that HTML part a link

HTML link to

User View

Displaying posts on user detail

To display user’s posts on the index page of user views/users/show.html.erb, we put extra feature:

  • user’s posts
  • user’s likes
  • tabs for switch between user’s posts and user’s likes
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<div class="main user-show">
  <div class="container">
    <div class="user">
      <img src="<%= "/user_images/#{@user.image_name}" %>">
      <h2><%= @user.name %></h2>
      <p><%= @user.email %></p>
      <% if @user.id == @current_user.id %>
        <%= link_to("Edit", "/users/#{@user.id}/edit") %>
      <% end %>
    </div>
    
    <ul class="user-tabs">
      <li class="active"><%= link_to("Posts", "/users/#{@user.id}") %></li>
      <li><%= link_to("Likes", "/users/#{@user.id}/likes") %></li>
    </ul>
    
    <% @user.posts.each do |post| %>
      <div class="posts-index-item">
        <div class="post-left">
          <img src="<%= "/user_images/#{post.user.image_name}" %>">
        </div>
        <div class="post-right">
          <div class="post-user-name">
            <%= link_to(post.user.name, "/users/#{post.user.id}") %>
          </div>
          <%= link_to(post.content, "/posts/#{post.id}") %>
        </div>
      </div>
    <% end %>
  </div>
</div>

In line number 20, there is posts method on @user instance. to enable this, we should add posts, a instance method on models/user.rb

1
2
3
4
5
6
class User < ApplicationRecord
  ...
  def posts
    return Post.where(user_id: self.id)
  end  
end

To enable view of user’s likes, we should create additional template views/users/likes.html.erb

User Likes

In views/users/likes.html.erb,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!-- Paste the HTML here -->
<div class="main user-show">
  <div class="container">
    <div class="user">
      <img src="<%= "/user_images/#{@user.image_name}" %>">
      <h2><%= @user.name %></h2>
      <p><%= @user.email %></p>
      <% if @user.id == @current_user.id %>
        <%= link_to("Edit", "/users/#{@user.id}/edit") %>
      <% end %>
    </div>
    
    <ul class="user-tabs">
      <li><%= link_to("Posts", "/users/#{@user.id}") %></li>
      <li class="active"><%= link_to("Likes", "/users/#{@user.id}/likes") %></li>
    </ul>
    
    <!-- Get each element of @likes using the each method -->
    <% @likes.each do |like| %>
      <!-- Define the post variable -->
      <% post = Post.find_by(id: like.post_id)%>
      
      <div class="posts-index-item">
        <div class="post-left">
          <img src="<%= "/user_images/#{post.user.image_name}" %>">
        </div>
        <div class="post-right">
          <div class="post-user-name">
            <%= link_to(post.user.name, "/users/#{post.user.id}") %>
          </div>
          <%= link_to(post.content, "/posts/#{post.id}") %>
        </div>
      </div>
    <!-- Add an end statement -->
    <% end %>
  </div>
</div>
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy