3

First StackOverFlow post, so bear with me :)

So I have a Post Model which has a Polymorphic Association with a Vote Model.

My main problem is ordering each post by the highest Votes. I'm trying to implement an Upvote system. QUESTION: How Do I Order Post by the Highest Votes? (@total)

It seems I have code in my views (@upcount, @downcount, @total) that I might perhaps have in my controller but I just have no clue how to do that.

Ideally I would want to do something like this in the community action : Post.order("@total") but obviously that won't work.

Post Model

class Post < ActiveRecord::Base 
belongs_to :user
has_many :votes, :as => :votable

Vote Schema:

t.integer  "upvote"
t.integer  "downvote"
t.string   "votable_type"
t.integer  "votable_id"
t.integer  "user_id"

Post Controller:

def community
@post = @votable = Post.all
end

And in My View Page (here's where things get tricky):

     <% @post.each do |post| %>
<div class="eachpostrate">
<% if signed_in? then %>
  <%= form_for [post, Vote.new] do |f| %>
    <%= f.hidden_field :downvote, :value => "0" %>
     <%= f.hidden_field :upvote, :value => "1" %>
     <%= f.submit "8", :class => "upvotethumbup" %>
       <% end %>
    <% if post.votes.empty? then %>
    <span class="upvotecount">
           <p> 0 </p>
      </span>
      <% else %>
      <% @upcount = [] %>
      <% @downcount = [] %>
      <span class="upvotecount">
      <p>
           <% post.votes.each do |vote| %>
           <% @upcount << vote.upvote %>
           <% @downcount << vote.downvote %>
           <% end %>
           <% @total = @upcount.sum - @downcount.sum %>
          <%= @total %>  
            </p>
    </span>
    <% end %>

I solved this using the directions that nilbus provided with the exception of putting the score method in my Post Model instead of Vote Model

 def score 
self.votes.upvotes.count - self.votes.downvotes.count 
  end

Otherwise, my posts are now ordered according to Upvote! Thanks everyone!

2 Answers 2

3

You should calculate the number of upvotes, downvotes, and total in your Post model instead. In general, try to put as much model-related code and logic in your model as you can, rather than in your views. That way you can use it again in other views too, and it's grouped more logically.

Specifically in this case, you'll want to use the methods that the model's associations provide, along with scopes.

class Vote < ActiveRecord::Base
  scope :upvotes, where(:upvote => 1)
  scope :downvotes, where(:downvote => 1)
end

With these scopes, you can count the votes on each post rather easily.

upvotes = post.votes.upvotes.count
downvotes = post.votes.downvotes.count
total = post.votes.count

You can calculate the score for a vote by subtracting downvotes from upvotes.

class Vote < ActiveRecord::Base
  def score
    upvotes.count - downvotes.count
  end
end

Then you can sort your posts using that score and then loop over them like you were in your view.

@posts = @posts.sort_by(&:score)

For best performance, it's a good idea to include all the votes when you first load the posts. To do that, you can use include in your controller. Otherwise, it will have to do extra queries to look up the votes when calculating the score.

def community
  @post = @votable = Post.includes(:votes).all
end

By the way - any reason you're creating two variables with the same content (@post and @votable)?

Sign up to request clarification or add additional context in comments.

5 Comments

Wow! Awesome response. @post = @votable seems to be what to do for Polymorphic so that if i were to refer to @votable it would refer to that @post where do I put upvotes, downvotes and total? in my views?
Yes, you can create those variables in your view inside the @posts.each loop. Or you can just refer to post.votes.upvotes.count directly.
Post.include gives me error message: NoMethodError private method `include' called for #<Class:0x0000012c254360>
Also, an error I'm receiving is for def score - it says that it can't call "count" on 1:FixNum
oops! Looks like I made a few mistake, all including the letter s. ;-) I made a few edits. include should have been includes. Then in the Vote scopes, the where condition should say where where(:upvote => 1) and where(:downvote => 1). The singular version is the attribute that is stored in your database, and the plural version is the scope I created. Sorry about that!
1
posts.sort {|x, y| (x.votes.collect(&:upvote).size - x.votes.collect(&:downvote).size) <=> (y.votes.collect(&:upvote).size - y.votes.collect(&:downvote).size) }

3 Comments

Also, did you know that you can use sort_by and avoid having to list the thing you're comparing twice? posts.sort {|x| x.votes.collect(&:upvote).size - x.votes.collect(&:downvote).size } Make it a little easier to read :-)
where would you put posts.sort etc.? In the view under the @posts.each block? I never knew you could sort inside the view; was always under the impression that it was strictly for controller
Good comments nibus! I was simply trying to get the answer in the fastest :P Hard to level up on this site lol! Your answer is much better than mine and so I will +1 it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.