Jak automatycznie posortować relację has_many w Railsach?

96

Wydaje się, że to naprawdę proste pytanie, ale nigdzie nie widziałem na nie odpowiedzi.

W szynach, jeśli masz:

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

Dlaczego nie możesz zamówić komentarzy za pomocą czegoś takiego:

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

Nazwany zakres działa, jeśli potrzebujesz dużo odwołań do niego, a nawet ludzie robią takie rzeczy:

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

Ale coś mi mówi, że powinno być prostsze. czego mi brakuje?

Brian Armstrong
źródło
Uważaj, używasz nieoczekiwanej metody: @ article.comments (reload = false) służy do wymuszenia pominięcia pamięci podręcznej (w celu wymuszenia ponownego załadowania relacji). Jeśli podasz hash, jest to to samo, co @ article.comments (true). Nie zapomnij użyć .all (: order => '...'). Już kilka razy złamałem nogę.
Marcel Jackwerth

Odpowiedzi:

152

Możesz określić porządek sortowania dla czystej kolekcji, korzystając z opcji has_manysamej:

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

Lub, jeśli potrzebujesz prostej, niebazowej metody sortowania, użyj sort_by :

article.comments.sort_by &:created_at

Zbierając to za pomocą metod zamawiania dodanych przez ActiveRecord:

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

Twój przebieg może się różnić: charakterystyka wydajności powyższych rozwiązań będzie się gwałtownie zmieniać w zależności od tego, w jaki sposób pobierasz dane i którego Rubiego używasz do uruchamiania aplikacji.

Jim Puls
źródło
Dzięki, „wszystko” jest chyba najprostsze. Dobry towar!
Brian Armstrong
58
w Rails 4 opcja zamówienia została usunięta. -> { order(created_at: :desc) }Zamiast tego użyj lambdy . Zobacz: stackoverflow.com/questions/18284606/…
d_rail
to zostało wycofane w przypadku rails 4, patrz stackoverflow.com/questions/18284606/ ...
bjelli
41

Od Rails 4 zrobiłbyś:

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

W przypadku has_many :throughrelacji kolejność argumentów ma znaczenie (musi być druga):

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

Jeśli zawsze będziesz chciał komentarzach dostępu w tej samej kolejności, bez względu na kontekst, można również zrobić to za pośrednictwem default_scopewewnątrz Commentjak:

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

Jednak może to być problematyczne z powodów omówionych w tym pytaniu .

Przed Railsami 4 można było określić orderjako klucz relacji, na przykład:

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

Jak wspomniał Jim, możesz również użyć sort_bypo pobraniu wyników, chociaż w każdym zestawie wyników o rozmiarze będzie to znacznie wolniejsze (i zużyje o wiele więcej pamięci) niż przy zamawianiu przez SQL / ActiveRecord.

Jeśli robisz coś, w którym dodanie domyślnego zamówienia jest z jakiegoś powodu uciążliwe lub chcesz zastąpić domyślne w niektórych przypadkach, trywialne jest określenie tego w samej akcji pobierania:

sorted = article.comments.order('created_at').all
Matt Sanders
źródło
1
Gdzie mogę to określić w samej akcji pobierania? Czy nadpisuję metodę w modelu?
Wit
@Wit - możesz dodać .order()do łańcucha metod, jak w ostatnim przykładzie. Czy o to pytasz?
Matt Sanders
Przepraszam. Nie pamiętam, co chciałem osiągnąć.
Wit
1
Przykład has_many poprzez polimorficzny jest tutaj bardzo pomocny!
Vijay
7

Jeśli używasz Railsów 2.3 i chcesz użyć tej samej domyślnej kolejności dla wszystkich kolekcji tego obiektu, możesz użyć default_scope do zamówienia swojej kolekcji.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Jeśli zadzwonisz

@students = @class.students

Zostaną one zamówione zgodnie z Twoim default_scope. TBH w bardzo ogólnym sensie porządkowanie jest jedynym naprawdę dobrym zastosowaniem domyślnych zakresów.

nitecoder
źródło
Od wersji Rails 4 nie jest to zgodne. Zobacz to rozwiązanie, aby uzyskać poprawną składnię Rails 4: stackoverflow.com/questions/18506038/rails-4-default-scope
Kees Briggs
0

A jeśli chcesz przekazać dodatkowe argumenty, takie jak dependent: :destroylub cokolwiek innego, powinieneś dołączyć je po lambdzie, na przykład:

class Article < ActiveRecord::Base 
  has_many :comments, -> { order(created_at: :desc) }, dependent: :destroy
end
Max L.
źródło