Как автоматически сортировать отношения has_many в Rails?

Это кажется очень простым вопросом, но я нигде не видел ответа.

В рельсах, если у вас есть:

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

Почему вы не можете заказывать комментарии примерно так:

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

Именованная область работает, если вам нужно ссылаться на нее много, и даже люди делают такие вещи:

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

Но что-то говорит мне, что это должно быть проще. Что мне не хватает?

Ответ 1

Вы можете указать порядок сортировки для голого набора с опцией самой has_many:

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

Или, если вам нужен простой метод сортировки без базы данных, используйте sort_by:

article.comments.sort_by &:created_at

Собирая это с помощью методов упорядочения ActiveRecord:

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

Ваш пробег может отличаться: характеристики производительности вышеупомянутых решений будут сильно меняться в зависимости от того, как вы извлекаете данные в первую очередь и какой Ruby вы используете для запуска своего приложения.

Ответ 2

Как и в Rails 4, вы должны:

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

При a has_many :through соотношение имеет порядок аргументов (он должен быть вторым):

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

Если вы всегда хотите получать комментарии в том же порядке, независимо от контекста, вы также можете сделать это через default_scope внутри Comment, например:

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

Однако это может быть проблематично для причин, обсуждаемых в этом вопросе.

Перед Rails 4 вы можете указать order как ключ в отношении, например:

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

Как сказал Джим, вы также можете использовать sort_by после того, как вы получили результаты, хотя в любых результирующих наборах размер будет значительно медленнее (и использовать намного больше памяти), чем выполнение заказа через SQL/ActiveRecord.

Если вы делаете что-то, когда добавление порядка по умолчанию является слишком громоздким по какой-либо причине или вы хотите переопределить свое значение по умолчанию в определенных случаях, тривиально указать его в самом процессе выборки:

sorted = article.comments.order('created_at').all

Ответ 3

Если вы используете Rails 2.3 и хотите использовать тот же порядок по умолчанию для всех коллекций этого объекта, вы можете использовать default_scope для заказа своей коллекции.

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Затем, если вы вызываете

@students = @class.students

Они будут заказываться в соответствии с вашим значением default_scope. TBH в очень общем порядке упорядочения является единственным действительно хорошим использованием областей по умолчанию.

Ответ 5

И если вам нужно передать некоторые дополнительные аргументы, например dependent: :destroy или что-то еще, вы должны добавить их после лямбда, например:

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