Reputation: 904
I'm trying to order my concerts by the number of reviews each has. Concert has_many reviews. Tried doing this
<% @concerts = Concert.all %>
<% @concerts.order(reviews.size).each do |concert| %>
-
-
-
<% end %>
but I get this error
undefined method `review' for ActiveRecord::Relation::ActiveRecord_Relation_Concert:0xb5540398>
Any suggestions how I would reference the number of reviews each has to order the concerts?
Upvotes: 3
Views: 782
Reputation: 11072
An alternative to the other answer which will ultimately give you the same result set, but will have slightly different side effects:
Concert.select("concerts.*, count(reviews.id) review_count")
.joins("LEFT JOIN reviews ON concerts.id = reviews.concert_id")
.group("concerts.id")
.order("review_count")
The main difference is that this query will not immediately execute until it is used; you'll receive an active record relation, just as you normally would when using any of the active record query methods, which means you can further refine or add to the query (hypothetically speaking).
Another difference is that this query does not require eager loading of reviews
. Depending on your needs, if you don't require any information from the related reviews, this query will run considerably faster.
As far as timing / performance goes, using a database of 50 concerts and 43867 reviews (index exists on FK), both versions seem to execute in approximately the same time (with eager loading). Here's a table of my benchmarking results (all results are in seconds):
| # | Sory_by Query | Pure AR Query | No eager loading |
--------------------------------------------------------
| 1 | 2.899806 | 2.840702 | 0.02164 |
| 2 | 3.150556 | 2.818374 | 0.21612 |
| 3 | 2.812867 | 3.025921 | 0.016368 |
| 4 | 3.069562 | 3.055307 | 0.016884 |
| 5 | 2.862722 | 2.680357 | 0.021316 |
|---|---------------|---------------|------------------|
AVG: 2.9591026 | 2.8841322 | 0.0584836 |
As you can see, there's no significant difference between the two queries using eager loading, and a major difference using my query above without eager loading. Obviously, the exact results will be different for you, but this should give you an idea of the relative differences.
Side note:
From what you posted in the question, it seems you are/were wanting to write this query in the ERB view. I would strongly advice moving this query to a method in the Concert
model, and creating an instance variable from the return of that method in the controller which the view can then use. That way you keep everything nice and separated.
Edit
To illustrate my recommendation, I would place the query in a class method of the Concert
model, like so:
def self.ordered_by_reviews
return select("concerts.*, count(reviews.id) review_count")
.joins("LEFT JOIN reviews ON concerts.id = reviews.concert_id")
.group("concerts.id")
.order("review_count")
end
Which you can call from your controller (doesn't matter which controller):
... other controller code:
@concerts = Concert.ordered_by_reviews
... and so on
You can then use @concerts
as you need and can remove any stuff like @concerts = Concert.all
and such.
Alternatively, you can also use scopes to do the same thing (which I believe would be considered more Rails-y anyways):
class Concert < ActiveRecord::Base
scope :ordered_by_review, -> { select("concerts.*, count(reviews.id) review_count").joins("LEFT JOIN reviews ON concerts.id = reviews.concert_id").group("concerts.id").order("review_count") }
... rest of class
Upvotes: 2
Reputation: 2493
Not the best, but the simplest solution is
@concerts = Concert.all.includes(:reviews).sort_by{|concert| concert.reviews.size}.reverse
Upvotes: 3