1

I have a Post, Reply and a Vote model (polymorphic):

 create_table "posts", :force => true do |t|
    t.text     "content",       :limit => 255
    t.integer  "user_id"
    t.datetime "created_at",                                  :null => false
    t.datetime "updated_at",                                  :null => false
    t.string   "title"
    t.integer  "replies_count",                :default => 0, :null => false
    t.integer  "category_id"
  end

  create_table "replies", :force => true do |t|
    t.text     "content"
    t.integer  "post_id"
    t.integer  "user_id"
    t.datetime "created_at", :null => false
    t.datetime "updated_at", :null => false
  end

  create_table "votes", :force => true do |t|
    t.integer  "votable_id"
    t.string   "votable_type"
    t.integer  "user_id"
    t.integer  "polarity"
    t.datetime "created_at",   :null => false
    t.datetime "updated_at",   :null => false
  end

I needed the total_votes of posts and replies so I created an instance method:

post.rb and reply.rb:

  def total_votes
    self.votes.map {|v| v.polarity }.sum
  end

So I can use it to sort posts and replies:

homepage:

 default_order = "created_at DESC"
 params[:order_by] ||= default_order
 @feed_items = @post.replies.paginate(page: params[:page],
                                      per_page: 10).order(params[:order_by])

So now, I'm not very sure what do add after order_by in the view:

  <span><%= link_to 'total_votes DESC', root_path(order_by: HERE) %></span>

I tried &:total_votes DESC and total_votes DESC but didn't work.

What's the right way of doing this? (I thought it was a bit of unnecessary to add a total_votes column to both posts and replies tables, but not sure if that is better for performance?)

alexchenco
  • 53,565
  • 76
  • 241
  • 413

1 Answers1

2

I would start with using the database to do the summing for you, but this is a little bit tricky because you need to order by a value that is calculated (an "aggregate"), but let's set that aside.

Your view will display items in the order determined when the instance variable is set in the controller. So in the controller, you may detect that there's an order_by parameter (e.g. /feed?order_by=total_votes, and load the @feed_items accordingly, e.g.

def index
  if params[:order_by] && params[:order_by] == 'total_votes'
    sort_order = "total_votes DESC"
  else
    sort_order = "created_at DESC"
  end
  @feed_items = @post.replies.paginate(page: params[:page],
                                  per_page: 10).order(sort_order)
end

then in your view, to change the sort order, create a link_to that adds the query string parameter (see the last example in the link_to api doc, e.g.

<%= link_to "Sort by Total Votes", root_path(:order_by => 'total_votes') %><br />
<%= link_to "Sort by Time", root_path %><br />

Your other question is about how to do the sorting by votes. This answer may provide a good start for that, and an answer to your performance question: Rails: Order by sum of two columns

Community
  • 1
  • 1
Tom Harrison
  • 13,533
  • 3
  • 49
  • 77
  • So, I have to add a `total_votes` column to the posts table and another `total_votes` column to the replies table before implementing your code? I'm aware that `:order_by => 'total_votes'` works with existing tables in the db. I just don't know how to do it with instance methods. – alexchenco Dec 04 '12 at 13:59
  • I just edited the answer. I think "no", you should calculate the total using `sum` on the fly. This might be a good case for a scope on the Votes model that you could invoke from your model (passing the required information), then replacing the `.order()` predicate. – Tom Harrison Dec 04 '12 at 14:03
  • I see, could you give me an example of the scope on the Votes model? – alexchenco Dec 04 '12 at 14:06
  • 1
    Check this section of the Rails guide on scopes: http://guides.rubyonrails.org/active_record_querying.html#passing-in-arguments – Tom Harrison Dec 04 '12 at 14:27
  • Ok, I checked it out. So I have to write a SQL statement to get the sum of the votes? Or use my current instance method but add scope at the very beginning? – alexchenco Dec 04 '12 at 14:34
  • Here's another example that demonstrates: http://stackoverflow.com/questions/10526012/order-players-on-the-sum-of-their-association-model – Tom Harrison Dec 04 '12 at 14:41
  • 1
    Thanks, I will try to figure out how to write the scope. – alexchenco Dec 04 '12 at 14:44