Wie sortiere ich automatisch eine has_many-Beziehung in Rails?

96

Dies scheint eine wirklich einfache Frage zu sein, aber ich habe nirgendwo eine Antwort darauf gesehen.

In Schienen, wenn Sie haben:

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

Warum können Sie die Kommentare nicht mit so etwas bestellen:

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

Der benannte Bereich funktioniert, wenn Sie häufig darauf verweisen müssen und sogar Leute solche Dinge tun:

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

Aber irgendetwas sagt mir, dass es einfacher sein sollte. Was vermisse ich?

Brian Armstrong
quelle
Seien Sie vorsichtig, Sie verwenden eine unerwartete Methode: @ article.comments (reload = false) dient zum Erzwingen eines Cache-Miss (um das Neuladen einer Beziehung zu erzwingen). Wenn Sie einen Hash angeben, entspricht dieser @ article.comments (true). Vergessen Sie nicht, .all (: order => '...') zu verwenden. Ich habe mir schon ein paar Mal das Bein gebrochen.
Marcel Jackwerth

Antworten:

152

Sie können die Sortierreihenfolge für die bloße Sammlung mit einer Option für sich has_manyselbst angeben :

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

Wenn Sie eine einfache Sortiermethode ohne Datenbank wünschen, verwenden Sie sort_by :

article.comments.sort_by &:created_at

Sammeln Sie dies mit den von ActiveRecord hinzugefügten Bestellmethoden:

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

Ihr Kilometerstand kann variieren: Die Leistungsmerkmale der oben genannten Lösungen ändern sich stark, je nachdem, wie Sie Daten zuerst abrufen und mit welchem ​​Ruby Sie Ihre App ausführen.

Jim Puls
quelle
Danke, das "Alles" ist wahrscheinlich das einfachste. Gutes Zeug!
Brian Armstrong
58
In Rails 4 wurde die Bestelloption entfernt. Verwenden Sie -> { order(created_at: :desc) }stattdessen ein Lambda . Siehe: stackoverflow.com/questions/18284606/…
d_rail
Dies wurde mit Schienen 4 veraltet, siehe stackoverflow.com/questions/18284606/…
bjelli
40

Ab Rails 4 würden Sie Folgendes tun:

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

Für eine has_many :throughBeziehung ist die Reihenfolge der Argumente wichtig (sie muss an zweiter Stelle stehen):

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

Wenn Sie unabhängig vom Kontext immer auf Kommentare in derselben Reihenfolge zugreifen möchten, können Sie dies auch über default_scopeFolgendes tun Comment:

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

Dies kann jedoch aus den in dieser Frage diskutierten Gründen problematisch sein .

Vor Rails 4 können Sie orderals Schlüssel für die Beziehung Folgendes angeben :

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

Wie Jim sagte, können Sie auch verwenden sort_by nachdem Sie Ergebnisse abgerufen haben, obwohl dies in allen Ergebnismengen der Größe erheblich langsamer ist (und viel mehr Speicher benötigt) als Ihre Bestellung über SQL / ActiveRecord.

Wenn Sie etwas tun, bei dem das Hinzufügen einer Standardreihenfolge aus irgendeinem Grund umständlich ist oder Sie Ihre Standardreihenfolge in bestimmten Fällen überschreiben möchten, ist es trivial, sie in der Abrufaktion selbst anzugeben:

sorted = article.comments.order('created_at').all
Matt Sanders
quelle
1
Wo kann ich es in der Abrufaktion selbst angeben? Überschreibe ich eine Methode im Modell?
Wit
@Wit - Sie können .order()der Methodenkette wie im letzten Beispiel hinzufügen . Fragen Sie das?
Matt Sanders
Tut mir leid. Ich kann mich nicht erinnern, was ich erreichen wollte.
Wit
7

Wenn Sie Rails 2.3 verwenden und für alle Sammlungen dieses Objekts dieselbe Standardreihenfolge verwenden möchten, können Sie Ihre Sammlung mit default_scope ordnen.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Dann, wenn Sie anrufen

@students = @class.students

Sie werden gemäß Ihrem default_scope bestellt. TBH ist im Allgemeinen die einzige wirklich gute Verwendung von Standardbereichen.

Nitecoder
quelle
Ab Rails 4 ist dies nicht konform. Die korrekte Rails 4-Syntax finden Sie in dieser Lösung: stackoverflow.com/questions/18506038/rails-4-default-scope
Kees Briggs
0

Und wenn Sie einige zusätzliche Argumente wie dependent: :destroyoder was auch immer übergeben müssen, sollten Sie die nach einem Lambda wie folgt anhängen:

class Article < ActiveRecord::Base 
  has_many :comments, -> { order(created_at: :desc) }, dependent: :destroy
end
Max L.
quelle