web-dev-qa-db-fra.com

Comment trier automatiquement une relation has_many dans Rails?

Cela semble être une question très simple mais je ne l’ai pas vue répondre nulle part.

Dans Rails si vous avez:

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

Pourquoi ne pouvez-vous pas commander les commentaires avec quelque chose comme ceci:

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

La portée nommée fonctionne si vous devez y faire beaucoup référence et que même les gens font ce genre de choses:

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

Mais quelque chose me dit que cela devrait être plus simple. Qu'est-ce que je rate?

93
Brian Armstrong

Vous pouvez spécifier l'ordre de tri de la collection nue avec une option sur has_many lui-même:

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

Ou, si vous souhaitez une méthode de tri simple, sans base de données, utilisez sort_by :

article.comments.sort_by &:created_at

Collecter ceci avec les méthodes de commande ajoutées à ActiveRecord:

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

Votre kilométrage peut varier: les performances des solutions ci-dessus vont changer énormément en fonction du mode de récupération des données et du type de Ruby que vous utilisez pour exécuter votre application.

145
Jim Puls

A partir de Rails 4, vous feriez:

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

Pour une relation has_many :through l'ordre des arguments est important (il doit être deuxième):

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

Si vous souhaitez toujours accéder aux commentaires dans le même ordre, quel que soit le contexte, vous pouvez également le faire via default_scope dans Comment comme:

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

Cependant, cela peut être problématique pour les raisons évoquées dans cette question .

Avant Rails 4, vous pouviez spécifier order comme clé de la relation, par exemple:

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

Comme Jim l'a mentionné, vous pouvez également utiliser sort_by après avoir récupéré les résultats, bien que, dans tous les ensembles de résultats, cette taille soit considérablement plus lente (et utilise beaucoup plus de mémoire) que si vous commandiez via SQL/ActiveRecord. 

Si vous faites quelque chose pour lequel l'ajout d'un ordre par défaut est fastidieux pour une raison quelconque ou si vous souhaitez remplacer votre ordre par défaut dans certains cas, il est simple de le spécifier dans l'action d'extraction elle-même:

sorted = article.comments.order('created_at').all
36
Matt Sanders

Si vous utilisez Rails 2.3 et souhaitez utiliser le même ordre par défaut pour toutes les collections de cet objet, vous pouvez utiliser default_scope pour commander votre collection.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Alors si vous appelez

@students = @class.students

Ils seront commandés selon votre default_scope. Dans un sens très général, TBH est la seule utilisation vraiment efficace des portées par défaut.

7
nitecoder

Vous pouvez utiliser la méthode de recherche d'ActiveRecord pour obtenir vos objets et les trier aussi.

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

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

6
vrish88

Et si vous devez passer des arguments supplémentaires tels que dependent: :destroy ou autre, vous devez les ajouter après un lambda, comme ceci:

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