The following code does the following: If the user, voted up show him a delete vote form and a vote down form. If the user voted down, show him a vote up form and the delete vote form. Otherwise just show the vote up and vote down form (I omitted some of the code so the question doesn’t get too long).

(scenario: user voted down):
posts_controller.rb:
def show
@post = Post.find(params[:id])
@replies = @post.replies.paginate(page: params[:page])
@reply = @post.replies.build
@vote = Vote.new
store_location
end
votes_controller.rb:
class VotesController < ApplicationController
before_filter :signed_in_user
def create
@votable = find_votable
# Destroy the vote first in case the user already voted
if already_voted?
@vote = @votable.votes.find_by_user_id(current_user.id)
@vote.destroy
end
@vote = @votable.votes.build(params[:vote])
@vote.user_id = current_user.id
@votable.save
respond_to do |format|
format.html { redirect_back }
format.js
end
end
def destroy
@votable = find_votable
@vote = @votable.votes.find_by_user_id(current_user.id)
@vote.destroy
@votable.reload
respond_to do |format|
format.html { redirect_back }
format.js
end
end
private
def find_votable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
def already_voted?
@votable.votes.exists?(:user_id => current_user.id)
end
end
posts/vote_form
<div class="vote-form">
<% if @post.votes.exists?(:user_id => current_user.id) %>
<% if @post.votes.find_by_user_id(current_user.id).polarity == -1 %>
<%= form_for ([@post, @vote]), remote: true do |f| %>
<%= f.hidden_field :polarity, value: 1 %>
<div class="form-actions">
<%= button_tag type: :submit, class: "btn btn-small vote" do %>
<i class="icon-thumbs-down"></i> Vote up
<% end %>
</div>
<% end %>
<%= form_for ([@post, @post.votes.find_by_user_id(current_user.id)]),
method: :delete,
remote: true do |f| %>
<div class="form-actions">
<%= button_tag type: :submit, class: "btn btn-small btn-primary unvote" do %>
<i class="icon-thumbs-up"></i> Vote down
<% end %>
</div>
<% end %>
<% end %>
votes/create.js:
$('#<%= @votable.class.name.downcase %>-<%= @votable.id %> .vote-form').html(
"<%= escape_javascript(render('shared/delete_vote')) %>"
);
shared/_delete_vote.html.erb:
<% if @votable.votes.find_by_user_id(current_user.id).polarity == -1 %>
<%= form_for ([@votable, @votable.votes.new]), remote: true do |f| %>
<div class="form-actions">
<%= button_tag type: :submit, class: "vote-down btn btn-small" do %>
<i class="icon-thumbs-up"></i> Vote up
<% end %>
</div>
<% end %>
<%= form_for ([@votable, @vote]), method: :delete, remote: true do |f| %>
<div class="form-actions">
<%= button_tag type: :submit, class: "vote-up btn btn-small btn-primary" do %>
<i class="icon-thumbs-up"></i> Vote down
<% end %>
</div>
<% end %>
<% end %>
So, now everything works fine, except that I get this error when I click vote down, and then vote up right after (without refreshing the page):
ActionView::Template::Error (undefined method `polarity' for nil:NilClass): 1: <% if @votable.votes.find_by_user_id(current_user.id).polarity == 1 %> 2: <%= form_for ([@votable, @vote]), method: :delete, remote: true do |f| %>
What could be the problem?
EDIT:
I realized that the line in the error message is the problem (not @votable):
<% if @votable.votes.find_by_user_id(current_user.id).polarity == 1 %>
Strange, I thought it was the same as
<% if @post.votes.find_by_user_id(current_user.id).polarity == 1 %>
I think the problem is here:
You destroy the vote for current user, so it can not be found inside view:
That’s why polarity is called on nil. You should reword the logic of your vote manipulations.
EDIT:
As the easiest solution, you may replace
with