To add the upvoting feature, let's install a new gem into our app.
Let's add the acts_as_votable
gem.
Take a few minutes to read the documentation
First, let's add the following line into our Gemfile
.
gem 'acts_as_votable', '~> 0.14.0'
Save the file and run bundle install
in your terminal to install the gem.
Next, go into your terminal and run the following commands:
rails generate acts_as_votable:migration
rails db:migrate
To make our Post
s votable, let's open up our post.rb
file and add in acts_as_votable
. Your post.rb
file should now look like this:
class Post < ApplicationRecord
belongs_to :user
has_many :comments, dependent: :destroy
has_one_attached :photo
validates :photo, :description, :user_id, presence: true
acts_as_votable
end
We also need to set the User
model as the voter. To do this, let's open up our user.rb
file and add in acts_as_voter
. Your user.rb
file should now look like this:
class User < ApplicationRecord
has_secure_password
has_many :posts, dependent: :destroy
has_many :comments, dependent: :destroy
has_one_attached :photo
acts_as_voter
end
Awesome. Now that we've got everything set up, in the next lesson, we're going to setup the backend for handling upvotes and downvotes.
This is what we first need to do:
If a user is signed in
If a user is not signed in
Let's work on displaying the icon and number of likes when a user is not signed in. We can use heroicons for getting the heart icon. Go to the heroicons site and search for heart and then copy svg. It should look something like below:
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
Now lets add the code above to each post inside the loop in posts index page above comments. Save the file and refresh the page. You should now see a heart.
Next, let's add the number of likes the post has. We can retrieve this with post.get_likes.size
. In order to display it in the form of "21 likes" or 1 like
, let's use the Rails pluralization engine to achieve this:
<div class="px-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
<%= pluralize post.get_likes.size, "like" %>
</div>
Save the file and refresh the page. It should now display "0 likes".
Next, let's work on the interface if the user is signed in. This is what should happen:
If the user has already liked the post
fill='red'
)If the user hasn't liked the post
fill='none'
)Let's write this in code.
According to the documentation, to find out if the user signed in has liked the post, we can use the voted_up_on?
method like this:
current_user.voted_up_on? post
# => returns true or false
Let's make our code look like this:
<div class="px-2">
<% if current_user.present? %>
<% if current_user.voted_up_on? post %>
<%= link_to post_downvotes_path(post.id), data: { turbo_method: :post } do %>
<svg xmlns="http://www.w3.org/2000/svg" fill="red" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
<% end %>
<% else %>
<%= link_to post_upvotes_path(post.id), data: { turbo_method: :post } do %>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
<% end %>
<% end %>
<% else %>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
</svg>
<% end %>
<%= pluralize post.get_likes.size, "like" %>
</div>
Notice how we specified the
method
to be:post
. By doing so, we are making sure we are making aPOST
request to the server instead of aGET
request.
Save the file and refresh the page. It should show no method error. Let's generate a controller upvotes under posts like rails g controller posts/upvotes
This should now generate an upvotes controller for us. Let's add the route for post_upvotes_path.
resources :posts do
resources :comments
resources :upvotes, only: :create, controller: 'posts/upvotes'
end
Repeat the same process for downvotes.
Go to the upvotes controller and implement logic that if current user is present then upvote the post by the current user. It should look something like:
def create
@post = Post.find(params[:post_id])
@post.liked_by current_user
end
Now if you press on one of the hearts, it will add a new like to the post! The number of likes for the posts should increase. Currently, you need to refresh the page to see the changes.
This is awesome, but having to reload the page every time you want to like a post is a really bad user experience.
In the next lessons, we're going to talk about what Turbo is, and how to make it so that we don't have to refresh the page every time we like a post.
"Turbo" typically refers to the Hotwire Turbo Streams and Turbo Frames features introduced in Rails 6 with the Hotwire framework. Hotwire is a set of tools developed by Basecamp to enhance the user experience in web applications by enabling real-time updates without the need for JavaScript frameworks like Stimulus or React.
Turbo Streams allow you to send updates from the server to the client in response to user actions without a full page reload. This is achieved through partial page updates using HTML and the Turbo Streams format.
Turbo Frames, on the other hand, allow you to isolate parts of your page and update them independently, again without requiring a full page reload.
We can learn more about turbo from this link