Implementing Ajax Like Button functionality in a Ruby on Rails application

I have a Ruby on Rails project that includes a User model and a Content model, among others. I recently implemented a feature where users can "like" content using the acts_as_votable gem.

Currently, the liking functionality works as expected but it requires a page refresh every time a user clicks the like button.

I'm interested in incorporating Ajax into this feature to allow for updating the like button and counter without refreshing the entire page.

Here's a snippet from my Content -> Show view:

<% if user_signed_in? %>
  <% if current_user.liked? @content %>
      <%= link_to "Dislike", dislike_content_path(@content), class: 'vote', method: :put %>
  <% else %>
      <%= link_to "Like", like_content_path(@content), class: 'vote', method: :put %>
  <% end %>
  <span> · </span>
<% end %>

<%= @content.get_likes.size %> users like this
<br>

The logic in the Content controller for liking/disliking is as follows:

def like
    @content = Content.find(params[:id])
    @content.liked_by current_user
    redirect_to @content
end

def dislike
    @content = Content.find(params[:id])
    @content.disliked_by current_user
    redirect_to @content
end

And in my routes.rb file, I've defined the following routes:

resources :contents do
    member do
        put "like", to: "contents#like"
        put "dislike", to: "contents#dislike"
    end
end

While the liking system is functional, it doesn't update the likes counter or the like button in real-time. To work around this, I currently use redirect_to @content in the controller action.

Is there a way to implement this behavior with an Ajax call? Any suggestions on improving this process?

Answer №1

If you want to implement this functionality, there are several approaches you can take. Here's a simple method:

Initial Steps

  1. To begin with, ensure that Rails UJS and jQuery are included in your application.js file (if they're not already there):

    //= require jquery
    //= require jquery_ujs
    
  2. Add remote: true to your link_to helpers:

    <%= link_to "Like", '...', class: 'vote', method: :put, remote: true %>
    
  3. Ensure that the controller responds appropriately to AJAX requests without redirection:

    def like
      @content = Content.find(params[:id])
      @content.liked_by current_user
    
      if request.xhr?
        head :ok
      else
        redirect_to @content
      end
    end
    

Enhanced Features

If you want to update the display showing how many users have liked the content, follow these steps:

  1. Assign an identifier to the counter value to make it easily retrievable using JavaScript later:

    <span class="votes-count" data-id="<%= @content.id %>">
      <%= @content.get_likes.size %>
    </span>
    users like this
    

    Make sure to use data-id instead of just id. Consider refactoring this snippet into a helper method for reusability.

  2. Update the controller response to provide the count along with additional information for locating the counter on the page (the keys may vary):

    #…
    if request.xhr?
      render json: { count: @content.get_likes.size, id: params[:id] }
    else
    #…
    
  3. Create some CoffeeScript (or other JS) code to handle the AJAX response:

    # This event is triggered by Rails after successful execution of link_to(remote: true)
    $(document).on 'ajax:success', 'a.vote', (status,data,xhr)->
      # The `data` object contains the decoded JSON data
      $(".votes-count[data-id=#{data.id}]").text data.count
      return
    

    Once again, utilize the data-id attribute to update only relevant counters.

Switching Between States

To dynamically change the link text from "like" to "dislike" and back, make the following adjustments:

  1. Modify your view as follows:

    <% if current_user.liked? @content %>
      <%= link_to "Dislike", dislike_content_path(@content), class: 'vote', method: :put, remote: true, data: { toggle_text: 'Like', toggle_href: like_content_path(@content), id: @content.id } %>
    <% else %>
      <%= link_to "Like", like_content_path(@content), class: 'vote', method: :put, remote: true, data: { toggle_text: 'Dislike', toggle_href: dislike_content_path(@content), id: @content.id } %>
    <% end %>
    

    This code could be placed in a helper method for cleaner organization (e.g., vote_link current_user, @content).

  2. Include the following CoffeeScript logic:

    $(document).on 'ajax:success', 'a.vote', (status,data,xhr)->
      # Update the counter
      $(".votes-count[data-id=#{data.id}]").text data.count
    
      # Toggle links
      $("a.vote[data-id=#{data.id}]").each ->
        $a = $(this)
        href = $a.attr 'href'
        text = $a.text()
        $a.text($a.data('toggle-text')).attr 'href', $a.data('toggle-href')
        $a.data('toggle-text', text).data 'toggle-href', href
        return
    
      return
    

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Executing a cross-site scripting (XSS) simulation within a MVC4 application on Visual Studio 201

Within my MVC 4 application, I am experimenting with simulating an XSS attack. The setup involves a button and a text box that simply outputs the entered value. However, when I input <script>alert('xss')</script> into the text box, an ...

Ways to retrieve the value of the variable within the confines of this particular

In my code, I have private variables in the constructor and public variables in the class. To reference these variables and functions, I use the "this" keyword. However, when trying to access these variables inside a function, I am getting an "undefined" ...

Ways to verify the click status of a button prior to initiating an AJAX request with jQuery?

I am facing an issue with a button that needs to be clicked by the user before submitting the form. Here's the code snippet for the button: $('#chooseButton') .on( 'click', function() { console.log("user ha ...

Determining the pageY value of a div element with overflow-y styling

Currently, I have implemented a script that tracks the mouse's position upon hover. My goal is to integrate this script within a div that has overflow-y: scroll The script currently utilizes pageY which identifies the position relative to the windo ...

Managing Modules at Runtime in Electron and Typescript: Best Practices to Ensure Smooth Operation

Creating an Electron application using Typescript has led to a specific project structure upon compilation: dist html index.html scripts ApplicationView.js ApplicationViewModel.js The index.html file includes the following script tag: <script ...

What is the correct way to handle fetch timeouts in a React component?

Utilizing a JavaScript timeout, I am able to fetch Dogs from my API successfully. However, there are instances where the timeout fails to clear properly: import { useState, useEffect, useCallback } from 'react'; const DogsPage = () => { c ...

Reasons Behind the News Not Being Retrieved Using [XML JS Query]

Can you help me troubleshoot my code? <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>News Site</title> <script> window.document.onload = ...

Show/Hide a row in a table with a text input based on the selected dropdown choice using Javascript

Can someone please assist me with this issue? When I choose Business/Corporate from the dropdown menu, the table row becomes visible as expected. However, when I switch back to Residential/Consumer, the row does not hide. My goal is to only display the row ...

Serious issue: a dependency request is an expression (Warning from Angular CLI)

I am currently exploring the dynamic loading of lazy child routes within a lazy routing module. For example: const serverResponse = [ { path: "transaction", children: [ { path: "finance", modulePath: &qu ...

Check if a value is present in the array with *ngIf

I'm curious about how to use the ngIf directive in a specific scenario. In my Angular application, I have dynamically generated angular material toggles, each with a unique id. I'm familiar with using ngIf to conditionally display elements on the ...

Tips for handling data strings using axios

In my node.js project, I am following a manual and attempting to display data obtained from jsonplaceholder app.get('/posts', async (req, res) => { const response = await axios.get('https://jsonplaceholder.typicode.com/posts'); ...

Utilize Ajax to execute a task based on the response received

Using jQuery Ajax, I am sending an id to another page where I select a column from a MySQL database and echo a row based on that id. While this part works well, the second function is not working. My question is, how can I make a click event happen on the ...

What is the best way to store user input in local storage using Vue.js?

Following the official Vue.js site, I have been trying to implement Framework 7. However, when using an input field, [object InputEvent] is displayed both when typing text and attempting to save it. Is there a way to store a name in local storage and have ...

Converting Node JS request to an API into React Fetch syntax

I have encountered a problem while developing an app in React-Native that connects with the Hubspot API. Initially, I tried to make the request using the Node JS request module, but it didn't work well with React Native when Expo was involved. Now, I ...

Generating a single JSON record in React Native

Need help displaying a single JSON record from an API request on the screen. const [user, setUser] = useState(); const getUserData = async () => { // {headers: {Authorization: "Basic " + base64.encode(username + ":" + passwor ...

JQuery Script Perform an Action - Pause - Execute Another Action

I'm working on a function that involves running some jQuery code, pausing for around 5 seconds, and then executing something else. Here's an example of what I'm trying to achieve: function myFunc() { var str1 = 'This is the starti ...

A backend glitch is exposed by NextJS in the web application

Currently, I am utilizing Strapi for my backend and have created a small script to handle authorization for specific parts of the API. Additionally, I made a slight modification to the controller. 'use strict'; const { sanitizeEntity } = require( ...

What is the best way to arrange this by DateTransaction using a dropdown list?

Requesting assistance from the PHP community! I'm a newbie and in need of your expertise. My task is to create a dropdown list that sorts a table based on the DateTransaction column, with options ranging from January to December. Here is the code sni ...

Transforming the initial character of a label element into uppercase

When I receive an external HTML page, all the data comes in lowercase. I attempted to capitalize the first letter of each label tag using CSS, but it ended up making the entire text uppercase instead. Here is what I tried: .fontmodal { text-transform ...

How can you create a smooth transition between two images in React Native?

I'm looking to create a cool effect with two images that gradually fade into each other. My initial approach involved layering one image over the other and adjusting its opacity using timing or animation functions, but I've been struggling to ge ...