¿Cómo clasifico automáticamente una relación has_many en Rails?

96

Esta parece una pregunta realmente simple, pero no la he visto respondida en ninguna parte.

En rieles si tienes:

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

¿Por qué no puedes ordenar los comentarios con algo como esto?

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

El alcance con nombre funciona si necesita hacer muchas referencias e incluso las personas hacen cosas como esta:

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

Pero algo me dice que debería ser más sencillo. ¿Qué me estoy perdiendo?

Brian Armstrong
fuente
Tenga cuidado, está utilizando un método inesperado: @ article.comments (reload = false) es para forzar un cache-miss (para forzar la recarga de una relación). Si proporciona un hash, es lo mismo que @ article.comments (verdadero). No olvide usar .all (: order => '...'). Ya me rompí la pierna varias veces.
Marcel Jackwerth

Respuestas:

152

Puede especificar el orden de clasificación para la colección básica con una opción en has_manysí misma:

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

O, si desea un método de clasificación simple que no sea de base de datos, use sort_by :

article.comments.sort_by &:created_at

Recopilando esto con los métodos de pedido agregados por ActiveRecord:

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

Su kilometraje puede variar: las características de rendimiento de las soluciones anteriores cambiarán enormemente según cómo esté obteniendo datos en primer lugar y qué Ruby esté utilizando para ejecutar su aplicación.

Jim Puls
fuente
Gracias, el "todos" es probablemente el más simple. ¡Buen material!
Brian Armstrong
58
en Rails 4, se ha eliminado la opción de pedido. En su lugar, use una lambda -> { order(created_at: :desc) }. Ver: stackoverflow.com/questions/18284606/…
d_rail
esto quedó obsoleto con los rieles 4, consulte stackoverflow.com/questions/18284606/…
bjelli
41

A partir de Rails 4, harías:

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

Para una has_many :throughrelación, el orden de los argumentos es importante (debe ser el segundo):

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

Si siempre va a querer a los comentarios de acceso en el mismo orden sin importar el contexto también se puede hacer esto a través de default_scopedentro Commentcomo:

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

Sin embargo, esto puede ser problemático por las razones discutidas en esta pregunta .

Antes de Rails 4, podía especificar ordercomo clave en la relación, como:

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

Como mencionó Jim, también puede usar sort_bydespués de haber obtenido los resultados, aunque en cualquier conjunto de resultados de tamaño, esto será significativamente más lento (y usará mucha más memoria) que hacer su pedido a través de SQL / ActiveRecord.

Si está haciendo algo en el que agregar un orden predeterminado es engorroso por algún motivo o si desea anular el predeterminado en ciertos casos, es trivial especificarlo en la acción de búsqueda en sí:

sorted = article.comments.order('created_at').all
Matt Sanders
fuente
1
¿Dónde puedo especificarlo en la propia acción de búsqueda? ¿Anulo un método en el modelo?
Wit
@Wit: puede agregar .order()a la cadena de métodos, como en el último ejemplo. ¿Es esto lo que estás preguntando?
Matt Sanders
Lo siento. No puedo recordar lo que estaba tratando de lograr.
Wit
¡Has_many, a través del ejemplo polimórfico es muy útil aquí!
Vijay
7

Si está utilizando Rails 2.3 y desea utilizar el mismo orden predeterminado para todas las colecciones de este objeto, puede utilizar default_scope para ordenar su colección.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Entonces si llamas

@students = @class.students

Se ordenarán según su default_scope. TBH en un sentido muy general, ordenar es el único uso realmente bueno de los ámbitos predeterminados.

nitecoder
fuente
A partir de Rails 4, esto no es compatible. Consulte esta solución para obtener la sintaxis correcta de Rails 4: stackoverflow.com/questions/18506038/rails-4-default-scope
Kees Briggs
0

Y si necesita pasar algunos argumentos adicionales como dependent: :destroyo lo que sea, debe agregar los que están después de una lambda, así:

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