Come faccio a ordinare automaticamente una relazione has_many in Rails?


96

Sembra una domanda molto semplice ma non ho visto risposte da nessuna parte.

In rails se hai:

class Article < ActiveRecord::Base 
  has_many :comments 
end 
class Comments < ActiveRecord::Base 
  belongs_to :article 
end

Perché non puoi ordinare i commenti con qualcosa del genere:

@article.comments(:order=>"created_at DESC")

L'ambito con nome funziona se è necessario fare spesso riferimento ad esso e anche le persone fanno cose come questa:

@article.comments.sort { |x,y| x.created_at <=> y.created_at }

Ma qualcosa mi dice che dovrebbe essere più semplice. Cosa mi sto perdendo?


Fai attenzione, stai usando un metodo inaspettato: @ article.comments (reload = false) serve per forzare un cache-miss (per forzare il ricaricamento di una relazione). Se fornisci un hash, è lo stesso di @ article.comments (vero). Non dimenticare di usare .all (: order => '...'). Mi sono già rotto una gamba un paio di volte.
Marcel Jackwerth

Risposte:


152

È possibile specificare l'ordinamento per la raccolta nuda con un'opzione su has_manyse stessa:

class Article < ActiveRecord::Base 
  has_many :comments, :order => 'created_at DESC'
end 
class Comment < ActiveRecord::Base 
  belongs_to :article 
end

Oppure, se desideri un metodo di ordinamento semplice e non basato sul database, usa sort_by :

article.comments.sort_by &:created_at

Raccolta di questo con i metodi di ordinazione aggiunti da ActiveRecord:

article.comments.find(:all, :order => 'created_at DESC')
article.comments.all(:order => 'created_at DESC')

Il tuo chilometraggio può variare: le caratteristiche delle prestazioni delle soluzioni di cui sopra cambieranno notevolmente a seconda di come stai recuperando i dati in primo luogo e di quale Ruby stai utilizzando per eseguire la tua app.


Grazie, il "tutto" è probabilmente il più semplice. Roba buona!
Brian Armstrong

58
in Rails 4, l'opzione order è stata rimossa. Usa -> { order(created_at: :desc) }invece un lambda . Vedi: stackoverflow.com/questions/18284606/...
d_rail

questa è stata sconsigliata con le rotaie 4, vedi stackoverflow.com/questions/18284606/...
bjelli

41

A partire da Rails 4, dovresti:

class Article < ActiveRecord::Base 
  has_many :comments, -> { order(created_at: :desc) }
end 
class Comment < ActiveRecord::Base 
  belongs_to :article 
end

Per una has_many :throughrelazione l'ordine degli argomenti è importante (deve essere il secondo):

class Article
  has_many :comments, -> { order('postables.sort' :desc) }, 
           :through => :postable
end

Se si vuole sempre ai commenti di accesso nello stesso ordine non importa il contesto si potrebbe anche farlo attraverso default_scopeall'interno Commentcome:

class Comment < ActiveRecord::Base 
  belongs_to :article 
  default_scope { order(created_at: :desc) }
end

Tuttavia questo può essere problematico per i motivi discussi in questa domanda .

Prima di Rails 4 potevi specificare ordercome chiave sulla relazione, come:

class Article < ActiveRecord::Base 
  has_many :comments, :order => 'created_at DESC'
end 

Come ha detto Jim, puoi anche usare sort_by dopo aver recuperato i risultati, sebbene in qualsiasi set di risultati di dimensioni questo sarà significativamente più lento (e utilizzerà molta più memoria) rispetto all'ordinamento tramite SQL / ActiveRecord.

Se stai facendo qualcosa in cui l'aggiunta di un ordine predefinito è macchinoso per qualche motivo o desideri sovrascrivere il tuo predefinito in alcuni casi, è banale specificarlo nell'azione di recupero stessa:

sorted = article.comments.order('created_at').all

1
Dove posso specificarlo nell'azione di recupero stessa? Sostituisco un metodo nel modello?
Wit

@Wit: puoi aggiungere .order()alla catena di metodi, come nell'ultimo esempio. È questo quello che stai chiedendo?
Matt Sanders

Mi dispiace. Non riesco a ricordare cosa stavo cercando di ottenere.
Wit

Il has_many, attraverso l'esempio polimorfico è di grande aiuto qui!
Vijay

7

Se stai usando Rails 2.3 e vuoi usare lo stesso ordinamento predefinito per tutte le collezioni di questo oggetto puoi usare default_scope per ordinare la tua collezione.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Allora se chiami

@students = @class.students

Verranno ordinati secondo il tuo default_scope. TBH in un senso molto generale l'ordinamento è l'unico uso veramente buono degli ambiti predefiniti.


A partire da Rails 4 questo non è conforme. Vedi questa soluzione per la sintassi corretta di Rails 4: stackoverflow.com/questions/18506038/rails-4-default-scope
Kees Briggs


0

E se hai bisogno di passare alcuni argomenti aggiuntivi come dependent: :destroyo altro, dovresti aggiungere quelli dopo un lambda, in questo modo:

class Article < ActiveRecord::Base 
  has_many :comments, -> { order(created_at: :desc) }, dependent: :destroy
end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.