gmoore
gmoore

Reputation: 5566

Selecting popular models based on count of has_many relationship in Rails

I have two models:

Novel has_many :pages
Page belongs_to :novel

I want to list popular Novels according to page count. Essentially, I want Novel models loaded from the outcome of this query:

select p.novel_id, count(*) as count
from pages p
GROUP BY p.novel_id
ORDER BY count DESC

I'm sure there's some cute way to do it in Rails 2.3 using named_scope, but I can't quite get it to work. Plus, if it does work, is it going to be dog slow?

I've considered keeping page_count on Novel, but that seems like a violation of something (convention, normalization, my soul).

Upvotes: 2

Views: 519

Answers (3)

hgmnz
hgmnz

Reputation: 13306

Seems like counter cache is the way to go. If you create a column called page_count on the novels table (with an index), Rails will cache the number of pages on the Novel model itself, making this kind of query very easy and performant.

The named_scope on the Novel model then becomes

class Novel < ActiveRecord::Base
  named_scope :popular, :order => 'page_count desc'
end

class Page < ActiveRecord::Base
  belongs_to :novel, :counter_cache => true
end

For more details check out the counter cache railscast

Upvotes: 5

Ryan Bigg
Ryan Bigg

Reputation: 107728

Keeping a counter_cache on the Novel is deemed acceptable in this matter and should aid your query.

In page.rb do:

 belongs_to :novel, :counter_cache => true

And in your novels table put a pages_count column. This will be automatically incremented when you create pages and decremented when you remove them.

Upvotes: 1

Jeff Paquette
Jeff Paquette

Reputation: 7127

Yep, that's going to be pretty slow. It's not a horrible thing to cache the page_count in your Novel. Normalization is all well and good, until it impacts performance.

Caching expensive calculations is the essence of most optimizations.

Upvotes: 2

Related Questions