Como eu classifico automaticamente um relacionamento has_many no Rails?

96

Esta parece uma pergunta muito simples, mas eu não vi uma resposta em lugar nenhum.

Nos trilhos, se você tiver:

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

Por que você não pode ordenar os comentários com algo assim:

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

O escopo nomeado funciona se você precisar referenciá-lo muito e até mesmo as pessoas fazem coisas como isto:

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

Mas algo me diz que deveria ser mais simples. o que estou perdendo?

Brian Armstrong
fonte
Tenha cuidado, você está usando um método inesperado: @ article.comments (reload = false) é para forçar um cache-miss (para forçar o recarregamento de uma relação). Se você fornecer um hash, é o mesmo que @ article.comments (true). Não se esqueça de usar .all (: order => '...'). Já quebrou minha perna algumas vezes.
Marcel Jackwerth

Respostas:

152

Você pode especificar a ordem de classificação da coleção básica com uma opção has_manyprópria:

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

Ou, se você quiser um método de classificação simples, sem banco de dados, use sort_by :

article.comments.sort_by &:created_at

Coletar isso com os métodos de pedido adicionados por ActiveRecord:

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

Sua milhagem pode variar: as características de desempenho das soluções acima mudarão radicalmente dependendo de como você está obtendo dados em primeiro lugar e de qual Ruby você está usando para executar seu aplicativo.

Jim Puls
fonte
Obrigado, o "tudo" é provavelmente o mais simples. Coisa boa!
Brian Armstrong,
58
no Rails 4, a opção de pedido foi removida. Em -> { order(created_at: :desc) }vez disso, use um lambda . Consulte: stackoverflow.com/questions/18284606/…
d_rail
isso foi descontinuado com rails 4, consulte stackoverflow.com/questions/18284606/…
bjelli
41

A partir do Rails 4, você faria:

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

Para um has_many :throughrelacionamento, a ordem do argumento é importante (deve ser a segunda):

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

Se você sempre vai querer comentários de acesso na mesma ordem não importa o contexto, você também pode fazer isso através de default_scopedentro Commentcomo:

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

No entanto, isso pode ser problemático pelas razões discutidas nesta pergunta .

Antes do Rails 4, você podia especificar ordercomo uma chave no relacionamento, como:

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

Como Jim mencionou, você também pode usar sort_by depois de obter os resultados, embora em qualquer conjunto de resultados de tamanho isso seja significativamente mais lento (e use muito mais memória) do que fazer o pedido por meio de SQL / ActiveRecord.

Se você estiver fazendo algo em que adicionar uma ordem padrão seja complicado por algum motivo ou se quiser substituir seu padrão em certos casos, é trivial especificá-lo na própria ação de busca:

sorted = article.comments.order('created_at').all
Matt Sanders
fonte
1
Onde posso especificá-lo na própria ação de busca? Devo substituir um método no modelo?
Sagacidade
@Wit - você pode adicionar .order()à cadeia de métodos, como no último exemplo. É isso que você está perguntando?
Matt Sanders,
Sinto muito. Não consigo me lembrar o que estava tentando alcançar.
Wit
O exemplo has_many, por meio de polimórfico, é muito útil aqui!
Vijay
7

Se você está usando Rails 2.3 e deseja usar a mesma ordem padrão para todas as coleções deste objeto, você pode usar default_scope para ordenar sua coleção.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Então se você ligar

@students = @class.students

Eles serão ordenados de acordo com seu default_scope. Em um sentido muito geral, a ordenação TBH é o único uso realmente bom dos escopos padrão.

nitecoder
fonte
No Rails 4, isso não é compatível. Veja esta solução para a sintaxe correta do Rails 4: stackoverflow.com/questions/18506038/rails-4-default-scope
Kees Briggs
0

E se você precisar passar alguns argumentos adicionais como dependent: :destroyou qualquer outro, você deve anexar aqueles após um lambda, como este:

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