2

I'm using "acts_as_votable" gem in my Rails application so that Users can vote on Posts. I'm trying to add Ajax functionality so that the entire page doesn't have to refresh when a user upvotes a post.

My upvote/downvote links are clickable and record the votes, but the render is not refreshing and the vote count still remains the same (I still have to refresh the page to see the count change).

I can see my in Run log this error:

Rendered posts/downvote.js.erb (3.4ms)
Completed 500 Internal Server Error in 77ms (ActiveRecord: 8.4ms)

ActionView::Template::Error (undefined method `id' for nil:NilClass):
    1: $(document).ready(function(){
    2:     $('#vote_<%= p.id %>').html("<%= escape_javascript render :partial => 'vote' %>");
    3: });
  app/views/posts/downvote.js.erb:2:in `_app_views_posts_downvote_js_erb__811382410496335987_70314739481440'
  app/controllers/posts_controller.rb:44:in `downvote'


  Rendered /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-4.2.5/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb (1.0ms)
  Rendered /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-4.2.5/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb (0.7ms)
  Rendered /usr/local/rvm/gems/ruby-2.3.0/gems/actionpack-4.2.5/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb (17.0ms)

Here is what I have:

routes.rb

  resources :posts do 
    member do
      put "like", to: "posts#upvote"
      put "dislike", to: "posts#downvote"
    end
  end

posts_controller.rb

def upvote
  @post = Post.find(params[:id])
  @post.upvote_by current_user
    respond_to do |format|
        format.html { redirect_to :back }
        format.js 
    end
end

def downvote
  @post = Post.find(params[:id])
  @post.downvote_by current_user
    respond_to do |format|
        format.html { redirect_to :back }
        format.js 
    end
end

explore.html.erb (/views/pages/explore.html.erb)

<% if @posts.each do |p| %>
<div class="panel-body">
  <p class="post-content"><%= auto_link(p.content, :html => { :target => '_blank' }) %></p>

  <%= render :partial => '/posts/vote', :class => "vote", :locals => { p: p } %>

</div>
<% end %>

_vote.html.erb (/views/posts/_vote.html.erb)

<<div id="vote_<%= p.id %>" class="vote">
  <%= link_to 'Vote Up', like_post_path(p), :class => "upvote", :method => :put, :remote => true %>
  <span><%= p.score %></span>  <!--show the current vote-->
  <%= link_to 'Vote Down', dislike_post_path(p), :class => "downvote", :method => :put, :remote => true %>
</div>

upvote.js.erb (/views/posts/upvote.js.erb)

$(document).ready(function(){
    $('#vote_<%= p.id %>').html("<%= escape_javascript render :partial => 'vote' %>");
});

downvote.js.erb (/views/posts/downvote.js.erb)

$(document).ready(function(){
    $('#vote_<%= p.id %>').html("<%= escape_javascript render :partial => 'vote' %>");
});

Is there something wrong with my javascript, or am I rendering something wrong? Thank you in advance.

naridi
  • 77
  • 2
  • 8

2 Answers2

2

The answer is there in the error, your p variable is nil in your javascript files is nil.

The reason you are able to use p in your html files is because you are looping through the @posts collection providing each post within a variable named p.

However in your js files you have no such loop, you therefore need to access the instance variable directly.

Also when you are rendering the _vote partial within the javascript file, you are not providing the p local variable, you will need to include that also.

change the downvote.js.erb code to:

$(document).ready(function(){
    $('#vote_<%= @post.id %>').html("<%= escape_javascript(render(:partial =>'vote', :locals => { p: @post })) %>");
});
John Hayes-Reed
  • 1,358
  • 7
  • 13
2
  1. _vote.html.erb (/views/posts/_vote.html.erb) "<<" in the first line will rise an syntax error.

  2. In your *.js.erb files just have @post from controller's action, not p like in your view. Try update like below:

upvote.js.erb (/views/posts/upvote.js.erb)

$(document).ready(function(){
  $('#vote_<%= @post.id %>').html("<%= escape_javascript render :partial => 'vote', locals: { p: @post } %>");
});

downvote.js.erb (/views/posts/downvote.js.erb)

$(document).ready(function(){
  $('#vote_<%= @post.id %>').html("<%= escape_javascript render :partial => 'vote', locals: { p: @post } %>");
});

Hope this help. :)

thanhnha1103
  • 1,037
  • 1
  • 8
  • 18
  • You are awesome Nhã! Thank you so much making this work and helping me to understand the controller's action function in this. – naridi Jan 12 '17 at 04:32