Ajaxified Like Button For Your Rails App
Tags: rails, webdev, beginners
Published: 2022-11-16 08:17:29


Having a like/favourite toggle for your products is a really cool feature to have for your Rails app. And Ajaxified like is a big plus+.

To have a like button in rails, all you need are these 3 tables/models.
1. Product (which can be liked or unliked).
2. User (someone who can like or unlike).
3. Like (Join-table since the user can like many products and a product can have likes from many users).

(Can skip this step if you already have an existing project.)
First of all, create a rails app. I am specifying the PostgreSQL database since most of the deployable apps need a strong database. Should work without PostgreSQL.

`rails new ajaxified_like --database=postgresql`

Now, we are going to create a Product model with a name and a description. I am going to use Scaffold(demo only) for this.

`rails g scaffold Product name description`

Once we create our Product, we need to create our user for which we are going to use devise gem. Add gem 'devise' in your gem file and bundle install . After bundling, you need to run this generator in your terminal.

    rails generate devise:install

You can follow the instructions given after running that line of code. Don’t tell anyone but, you can skip those if it is only for demo purposes. I didn’t though. So be like me. Now we will generate a devise User.

    rails generate devise User

Now, we will have to migrate all the migration files till now with rails db:migrate . If this is your new project, you might need to create your database with rails db:create before migrating.

Finally, we can to move to next step to create our like (join table).

`rails g model like user:references product:references`

Don’t forget to migrate with rails db:migrate . Now, you prettify your [http://127.0.0.1:3000/products](http://127.0.0.1:3000/products) after some filler seeds. You might have something better than this.

![](https://cdn-images-1.medium.com/max/2800/1*-R01-2qjrPHTuVd1CZsh_Q.png)

Your should have something like this in your project index.html.erb file.

    #/app/views/products/index.html.erb

    <div class="container">

    <h1>Products</h1>

    <% @products.each do |product| %>

    <div class="product-card">

    <div class="product-info">

    <h4><strong><%= product.name %></strong></h4>

    <p><%= product.description %></p>

    </div>

    <%# We are giving our like-box a unique class for selecting them. %>

    <div class="like-box like-<%= product.id %>">

    <%# the line below will be changed to with partial %>

    <%#= render 'like_button', product: product %>

    <i class="fa fa-heart" aria-hidden="true"></i>

    </div>

    </div>

    <% end %>

    </div>

You might want to log in before we move forward. So, create a user instance and can manually log in through this [http://127.0.0.1:3000/users/sign_](http://127.0.0.1:3000/users/sign_up)in .

Before making a post request to like controller, we need to config our model files a little bit.

    # app/models/user.rb

    class User < ApplicationRecord

    # Include default devise modules. Others available are:

    # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable

    devise :database_authenticatable, :registerable,

    :recoverable, :rememberable, :validatable

    has_many :likes, dependent: :destroy

    end

    # app/models/product.rb

    class Product < ApplicationRecord

    has_many :likes, dependent: :destroy

    # To get array of users who liked the
     product

    has_many :users, through: :likes

    # instance method for product to check if the current user is liked or not.

    def liked(current_user)

    (users.include? current_user)

    end

    end

    # app/models/like.rb

    class Like < ApplicationRecord

    belongs_to :user

    belongs_to :product

    # create unique instance of like

    validates :user, uniqueness: { scope: :product }

    end

Now, we will configure our routes like so.

    # config/routes.rb

    Rails.application.routes.draw do

    root "products#index"

    devise_for :users

    resources :products  do

    # we are nesting so we can access product_id

    resources :likes, only: [:create]

    end

    resources :likes, only: [:destroy]

    end

Then,we have to create to create our partial ‘like_button’. I have something like this in my partial . We need to uncomment our render from index.html.erb and comment out the previous font-awesome icon.

    <%# app/views/products/_like_button.html.erb %>

    <%# Checking if user signed in or not. If your product has user

    you might want to check if (current_user != product.user)

    %>

    <% if user_signed_in? %>

    <%# if product is liked delete existing like instance on click %>

    <% if product.liked(current_user) %>

    <%# Best way will be to write a helper method for getting like instance. ***(remote: true) ask for js response %>

    <%= link_to like_path(Like.where(user: current_user ,product: product).first), remote: true, method: :DELETE, class: "liked" do %>

    <i class="fa fa-heart" aria-hidden="true"></i>

    <% end %>

    <%# if product is not liked,create a new like instance on click %>

    <% else %>

    <%= link_to pr oduct_likes_path(product) , method: :POST, remote: true, class: "not-liked" do %>

    <i class="fa fa-heart" aria-hidden="true"></i>

    <% end %>

    <% end %>

    <% else %>

    <i class="fa fa-heart not-liked" aria-hidden="true"></i>

    <% end %>

    <p>

    <%= product.likes.count %>

    </p>

Almost there. Bear with me. Now, we will create our like controller. Easy right! rails g controller likes Inside likes controller, we will need only create and delete method.

    # app/controllers/likes_controller.rb

    class LikesController < ApplicationController

    def create

    @product = Product.find(params[:product_id])

    @like = Like.new(product: @product, user: current_user)

    if @like.save

    # respond html if javascript is disabled.

    respond_to do |format|

    format.html { redirect_to products_path }

    format.js

    end

    else

    redirect_to products_path

    end

    end

    def destroy

    @like = Like.find(params[:id])

    @product = @like.product

    if @like.destroy

    # respond html if javascript is disabled.

    respond_to do |format|

    format.html { redirect_to products_path }

    format.js

    end

    else

    redirect_to products_path

    end

    end

    end

Now we are going to make JS response for create and destroy. To do so, we will have to create ‘create.js.erb’ and ‘destroy.js.erb’. Both will contain this line of code.

    #app/views/likes/create.js.erb
    #app/views/likes/destroy.js.erb

    document.querySelector(".like-<%= @product.id %>").innerHTML = "<%= j render 'products/like_button', product: @product %>";

    # Here we are selecting like-box with there unique class.
    # document.querySelector(".like-<%= @product.id %>")

    # Then we are overwriting it's inner html content with new render
    # like-button.

Done. This should do the trick. If you made through to the end, best of luck for ajaxified development.

You final output should be working like this. If not, feel free to leave you errors in comment.
{% embed https://www.youtube.com/watch?v=-Jmgto_x_dg %}