¿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?


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.


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

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.


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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.