Comment trier automatiquement une relation has_many dans Rails?

96

Cela semble être une question très simple mais je n'ai vu de réponse nulle part.

Dans les 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 avez besoin de la référencer beaucoup et même les gens font des choses comme ceci:

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

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

Brian Armstrong
la source
Attention, vous utilisez une méthode inattendue: @ article.comments (reload = false) sert à forcer un cache-miss (pour forcer le rechargement d'une relation). Si vous fournissez un hachage, c'est la même chose que @ article.comments (true). N'oubliez pas d'utiliser .all (: order => '...'). Je me suis déjà cassé la jambe plusieurs fois.
Marcel Jackwerth

Réponses:

152

Vous pouvez spécifier l'ordre de tri pour la collection nue avec une option sur has_manyelle-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 voulez 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 caractéristiques de performance des solutions ci-dessus changeront énormément en fonction de la manière dont vous récupérez les données en premier lieu et du Ruby que vous utilisez pour exécuter votre application.

Jim Puls
la source
Merci, le "tout" est probablement le plus simple. Bon produit!
Brian Armstrong
58
dans Rails 4, l'option de commande a été supprimée. Utilisez -> { order(created_at: :desc) }plutôt un lambda . Voir: stackoverflow.com/questions/18284606/…
d_rail
cela a été dépréciée avec des rails 4, voir stackoverflow.com/questions/18284606/...
bjelli
41

À 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 has_many :throughrelation, l'ordre des arguments est important (il doit être le deuxième):

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

Si vous voulez toujours aux commentaires d'accès dans le même ordre peu importe le contexte , vous pouvez aussi le faire par l' default_scopeintérieur Commentcomme:

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 ordercomme clé de la relation, comme:

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

Comme Jim l'a mentionné, vous pouvez également utiliser sort_byaprès avoir récupéré les résultats, bien que dans tous les ensembles de résultats de taille, cela soit beaucoup plus lent (et utilise beaucoup plus de mémoire) que de passer votre commande via SQL / ActiveRecord.

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

sorted = article.comments.order('created_at').all
Matt Sanders
la source
1
Où puis-je le spécifier dans l'action de récupération elle-même? Dois-je remplacer une méthode dans le modèle?
Wit
@Wit - vous pouvez ajouter .order()à la chaîne de méthodes, comme dans le dernier exemple. Est-ce ce que vous demandez?
Matt Sanders
Je suis désolé. Je ne me souviens pas de ce que j'essayais d'accomplir.
Avec
1
L'exemple has_many, à travers polymorphique, est super utile ici!
Vijay
7

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 ordonner votre collection.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Alors si vous appelez

@students = @class.students

Ils seront classés selon votre default_scope. Le classement TBH dans un sens très général est la seule très bonne utilisation des portées par défaut.

nitecoder
la source
À partir de Rails 4, ce n'est pas conforme. Voir cette solution pour la syntaxe correcte de Rails 4: stackoverflow.com/questions/18506038/rails-4-default-scope
Kees Briggs
0

Et si vous avez besoin de passer des arguments supplémentaires comme dependent: :destroyou autre, vous devez ajouter ceux après un lambda, comme ceci:

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