Search code examples
javascriptruby-on-railsajaxacts-as-votable

Rails Acts_as_votable ajax/js upvoting all posts instead of one


Okay, I'm starting to pull my hair out on this one. I'm new to rails, and was following a tutorial on making a pinterest style app. I finished it but wasn't happy with the up-voting system.

It was refreshing the page every time I clicked to up vote. So I found some post about it and did exactly what it said. It kept loading a page that showed me the js code instead of executing it.

This morning I changed a "put" to "get" then back again, and now it's working... NO IDEA what happened to change it.

BUT, now it's up voting every post on the page instead of just the one I click on. When I hover over the links, they all point to the proper ID for that link.

Here's the code from the controller:

def upvote
@pin.upvote_by current_user
respond_to do |format|
  format.html { redirect_to :back }
  format.js { render :layout => false }
  format.json 
end
end

And the view (haml):

.btn-group.pull-right
      = link_to like_pin_path(pin), method: :put, remote: true, class: "btn btn-like" do
        %span.glyphicon.glyphicon-heart
          = pin.get_upvotes.size

Upvote.js.erb:

$('.glyphicon-heart').html('<%=@pin.get_upvotes.size%>');

Routes.rb

Rails.application.routes.draw do
  devise_for :users
  resources :pins do
    member do
      put "like", to: "pins#upvote", defaults: { format: 'js' }
    end
  end

  root "pins#index"
end

Here's the log from the console when a like button is clicked:

Started PUT "/pins/10/like" for ::1 at 2015-12-08 11:40:02 -0600
Processing by PinsController#upvote as JS
Parameters: {"id"=>"10"}
Pin Load (0.2ms)  SELECT  "pins".* FROM "pins" WHERE "pins"."id" = ? LIMIT 1  [["id", 10]]
User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ?  ORDER BY "users"."id" ASC LIMIT 1  [["id", 2]]
(0.3ms)  SELECT COUNT(*) FROM "votes" WHERE "votes"."votable_id" = ? AND "votes"."votable_type" = ? AND "votes"."voter_id" = ? AND "votes"."voter_type" = ? AND "votes"."vote_scope" IS NULL  [["votable_id", 10], ["votable_type", "Pin"], ["voter_id", 2], ["voter_type", "User"]]
ActsAsVotable::Vote Load (0.6ms)  SELECT  "votes".* FROM "votes" WHERE "votes"."votable_id" = ? AND "votes"."votable_type" = ? AND "votes"."voter_id" = ? AND "votes"."voter_type" = ? AND "votes"."vote_scope" IS NULL  ORDER BY "votes"."id" DESC LIMIT 1  [["votable_id", 10], ["votable_type", "Pin"], ["voter_id", 2], ["voter_type", "User"]]
(0.2ms)  begin transaction
(0.1ms)  commit transaction
(0.2ms)  SELECT COUNT(*) FROM "votes" WHERE "votes"."votable_id" = ? AND "votes"."votable_type" = ? AND "votes"."vote_flag" = ? AND "votes"."vote_scope" IS NULL  [["votable_id", 10], ["votable_type", "Pin"], ["vote_flag", "t"]]
Rendered pins/upvote.js.erb (4.4ms)
Completed 200 OK in 25ms (Views: 9.4ms | ActiveRecord: 1.8ms)

I'm sure it's something really simple but ajax/js are even newer to me than rails.

Let me know if there's anything else I need to post. Any help will be so greatly appreciated!

Also for future reference, what would make the link load a page with the js code instead of executing the code? Since I don't feel like I changed anything that drastic today, I don't know how I got past that. I'm glad I did, but it would be helpful to know HOW.


Solution

  • It's doing that because your JS is technically targeting all the links with that class. Instead you could append the model's id of the individual pin to the link's class or create a data attribute but it's entirely up to you. In doing so it'll only target the one link that's clicked.

    UPDATE:

    Essentially you'll want to assign a class (instance's id) to a container div holding the vote button: pin-#{pin.id} for index view and pin-#{@pin.id} for show view.

    Controller

    def upvote
      @pin.upvote_by current_user
    
      respond_to do |format|
        format.js { render "upvote.js.erb" }
      end
    end
    

    Routes

    ...
    resources :pins do
      member do
        put :upvote
      end
    end
    

    Index View

    ...
    %div{class: "btn-group pull-right pin-#{pin.id}"}
      = render "upvote", pin: pin
    

    Show View

    ...
    .col-md-6
        %div{class: "btn-group pull-right pin-#{@pin.id}"}
          = render "upvote", pin: @pin
          -if user_signed_in?
          = link_to "Edit", edit_pin_path, class: "btn btn-default"
          = link_to "Delete", pin_path, method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-default"
    

    _upvote.html.haml

    = link_to upvote_pin_path(pin), method: :put, remote: :true, class: "btn btn-default btn-like" do
      %span.glyphicon.glyphicon-heart
      = pin.get_upvotes.size
    

    upvote.js.erb

    $(".pin-<%= @pin.id %>").html("<%= escape_javascript(render 'upvote', pin: @pin) %>");