2009-06-05 8 views
6

Diciamo che ho un modello di libro e un modello di autore. Voglio elencare tutti gli autori ordinati in base al loro numero di libri. Qual è il modo migliore per farlo?Come ordinare gli autori in base al numero di libri con ActiveRecord?

So come farlo in SQL, sia facendo dove ... con una selezione nidificata o con qualche join. Ma quello che mi piacerebbe sapere è come farlo bene con ActiveRecord.

risposta

9

Come Kevin ha suggerito, counter_cache è l'opzione più semplice, sicuramente quello che avrei usato.

class Author < ActiveRecord::Base 
    has_many :books, :counter_cache => true 
end 

class Book < ActiveRecord::Base 
    belongs_to :author 
end 

E se si sta utilizzando Rails 2.3 e si vorrebbe che questa sia la Ordinamento predefinito è possibile utilizzare il nuovo metodo default_scope:

class Author < ActiveRecord::Base 
    has_many :books, :counter_cache => true 

    default_scope :order => "books_count DESC" 
end 

books_count è il campo che esegue il comportamento contatore di caching, e probabilmente c'è un modo migliore di utilizzarlo direttamente nell'oscilloscopio predefinito, ma ti dà l'idea e il lavoro sarà fatto.

EDIT:

In risposta al commento chiedendo se counter_cache funzionerà se un applicazione Rails non altera i dati, ben si può, ma non nel modo predefinito come Rails aumenti e le diminuzioni il contatore a risparmiare tempo. Quello che potresti fare è scrivere la tua implementazione in un callback after_save.

class Author < ActiveRecord::Base 
    has_many :books 

    after_save :update_counter_cache 

    private 
    def update_counter_cache 
     update_attribute(:books_count, self.books.length) unless self.books.length == self.books_count 
    end 
end 

Ora non si dispone di un counter_cache installato, ma se è il nome del campo nel books_count banca dati come da convenzione counter_cache poi quando si guarda:

@Author = Author.find(1) 
puts @author.books.size 

Sarà comunque possibile utilizzare la contatore in cache invece di eseguire una ricerca nel database. Ovviamente questo funzionerà solo quando l'app per i rails aggiorna la tabella, quindi se un'altra app fa qualcosa allora i tuoi numeri potrebbero non essere sincronizzati fino a quando l'applicazione rails non sarà ripristinata. L'unico modo per aggirare ciò che posso pensare è un cron job per sincronizzare i numeri se la tua app non esegue ricerche abbastanza spesso da renderlo non importa.

+0

fa il contatore nella cache se un'applicazione non-rails aggiorna direttamente i dati nel database (ad esempio rimuove un libro)? – dplante

+0

Suppongo di no, almeno finché non si salva di nuovo il record dall'app di Rails. –

7

Suggerisco di contrapporre il conteggio del libro come un altro attributo su Autore (Rails supporta questo con un'opzione sull'associazione). Questo è di gran lunga il metodo più veloce, e Rails è abbastanza buono per assicurarsi che mantenga il conteggio in sincronia.

+2

Grazie per la risposta rapida e accurata. Ho usato la tua risposta per cercare ulteriori informazioni e ho trovato http://railscasts.com/episodes/23-counter-cache-column che è molto utile. Ho selezionato la risposta di railsninja perché è più completa e aiuterebbe i passanti a migliorare. –

9

Tratto da http://www.ruby-forum.com/topic/164155

Book.find(:all, :select => "author_id, count(id) as book_count", :group => "author_id", :order => "book_count") 
+0

Giusto. In pratica si tratta solo di eseguire una query SQL e ottenere il DBMS per eseguire il lavoro, ed è il modo più semplice e migliore per farlo. In particolare, si desidera eseguire questa operazione per evitare di eseguire molte query (probabilmente anche una sola per autore) quando una query eseguirà ciò di cui si ha bisogno. –

3

Assumendo rotaie 3

@authors=Authors.include(:books).sort do |a,b| 
    a.books.size <=> b.books.size 
end 

Nota: Questa volontà restituisce un array di dischi, non un ActiveRecord: Relation.

Nota2: Usa dimensione, non conta, poiché il conteggio eseguirà le chiamate sql al db.

Problemi correlati