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