1

I have this bit of code where each project can be voted on. In my views I want to list each project by the order of votes from highest to lowest.

I found one way, but is there a better way to do this?

My way:

Controller

class Admin::ProjectsController < ApplicationController
  def index
     @projects = Project.all.sort_by { |project| project.votes.count }
  end
end

Views

- @projects.each do |project|
  %li= project.name
  %li= project.teacher_firstname
  %li= project.teacher_lastname
  %li= project.votes.count

I was trying to do something along the lines of Project.votes.all.order(:desc) but obviously that doesn't work, but that's what I want to try to do.

And then someone pointed me to :counter_cache but that seems a little too much just to count. No need to add a count column.

Any other suggestions?

Update Okay so two things

  1. @jgautsch's worked, but I'm curious, it's a class method wouldn't I want to make this an instance method? Or would it not matter? Just take off self? I sometimes get confused (as much as read articles on this) of when it's appropriate for instance verses class method.

  2. Also, I noticed that I only get projects that have been voted on. How would I continue to display them even if there are 0 votes? I even took out the limit and the num argument

gary1410
  • 309
  • 1
  • 5
  • 17

2 Answers2

2

Check out this question: Rails 3 ActiveRecord: Order by count on association

As described in the linked question, you can either use counter_cache and keep your query to one table (nothing wrong with adding a column here), or do something like this (untested):

class Project
  has_many :votes

  def self.top_voted(num = 10)
    select("projects.id, OTHER_ATTRS_YOU_NEED, count(votes.id) AS votes_count")
    .joins(:votes)
    .group("projects.id")
    .order("votes_count DESC")
    .limit(num)
  end
end

Project.top_voted(5) # top 5 most voted projects
Community
  • 1
  • 1
jgautsch
  • 291
  • 2
  • 5
  • actually so if I do this is this is class method and not an instance method. I guess it wouldn't really matter but I'd have to change up the way I display them in the views, right? – gary1410 Jan 26 '15 at 04:54
  • I'd take out the period after the group statement. Also, I noticed that I only get projects that have been voted on. How would I continue to display them even if there are 0 votes? I took out the limit statement and no dice. – gary1410 Jan 26 '15 at 06:27
  • `.limit` here just limits the number of results. It's typically the best idea to try and pull all records from db, so you might consider using a pagination gem instead of limit. To include `Projects` with 0 votes, just remove the `.joins(:votes)` line – jgautsch Jan 26 '15 at 14:41
1

Interesting question. You can do this:

Project.select('*, (SELECT count(*) FROM votes WHERE votes.project_id=project.id) as vote_count').order("vote_count DESC")

Ugly, I know, but at least its just 1 query.

Would be cool if there's a fully ORM way to do that, something like:

Project
  .select(vote_count: Vote.where("project_id = project.id").count)
  .order(vote_count: :desc)

UPDATE: another functional way:

Project
  .select(
    Vote
      .select('count(*)')
      .where('project_id=projects.id')
      .to_sql.sub(/(.*)/, '*, (\1)as vote_count'))
  .order("vote_count DESC")

or maybe we can achieve a more ORMed by using Arel directly or search by Vote (and join with Project inside) instead of search by Project (and join with Vote inside).

Community
  • 1
  • 1
Daniel Loureiro
  • 4,595
  • 34
  • 48
  • Thanks for the input, but I'm not sure where you're going with this one, because you're selecting from votes? I tried it and I get `missing FROM-clause entry for table "project" LINE 1: ...SELECT count(*) FROM votes WHERE votes.project_id=project.id...` I mean semantically but it doesn't pull through the views. I just did `@projects = Project.select('(SELECT count(*) FROM votes WHERE votes.project_id=project.id)` as vote_count').order("vote_count") in the controller and used the same views from above. – gary1410 Jan 26 '15 at 06:33
  • was a typo: I wrote `project_id=project.id` but the correct is project**s**, with a "s": `project_id=projects.id` – Daniel Loureiro Jan 26 '15 at 06:38
  • sorry, was another error. The 2 queries was missing a '*' to be selected. Also, you want to order in a inverse way. I update the 2, and they should work. – Daniel Loureiro Jan 26 '15 at 06:57
  • Oh great, that works! Now I have to figure out why @jgautsch's query doesn't return all the projects even if they have 0 votes. – gary1410 Jan 26 '15 at 07:25
  • Try to change `joins(:votes)` to `joins("left outer join votes on (project_id=projects.id)")` – Daniel Loureiro Jan 26 '15 at 07:30
  • Okay that works. What I've learned through all of this is I don't like ActiveRecords Query Language...I'm not sure how the arguments are passed in things like `.join` or `select` or `.group`. I'm much better off used `.find_by_sql`. Second, I used his exact query but I just stuck this in `.having("votes_count > -1)` – gary1410 Jan 26 '15 at 08:07
  • I guess overall lesson, rails won't let you someone query through objects in an object oriented way. You just basically have to use SQL, unless I'm mistaken. – gary1410 Jan 26 '15 at 08:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/69583/discussion-between-gary1410-and-daniel-loureiro). – gary1410 Jan 26 '15 at 08:12