1. Implement votable

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

Writing the front end

This is what we first need to do:

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:

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 a POST request to the server instead of a GET request.

Writing the backend

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.

What is Turbo?

"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

Lesson list