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?


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.


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

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.


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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.