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

96

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

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

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 }

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

Брайан Армстронг
источник
Будьте осторожны, вы используете неожиданный метод: @ article.comments (reload = false) - это принудительный промах кеширования (принудительная перезагрузка отношения). Если вы укажете хеш, это то же самое, что и @ article.comments (true). Не забудьте использовать .all (: order => '...'). Сломал ногу уже несколько раз.
Марсель Джекверт,

Ответы:

152

Вы можете указать порядок сортировки для чистой коллекции с опцией для 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 вы используете для запуска своего приложения.

Джим Пульс
источник
Спасибо, "все" наверное самое простое. Хорошая вещь!
Брайан Армстронг,
58
в Rails 4 опция порядка удалена. -> { order(created_at: :desc) }Вместо этого используйте лямбду . См .: stackoverflow.com/questions/18284606/…
d_rail
это устарело с рельсами 4, см. stackoverflow.com/questions/18284606/…
bjelli
41

Начиная с Rails 4, вы должны:

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

Для 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
Мэтт Сандерс
источник
1
Где я могу указать это в самом действии загрузки? Могу ли я переопределить метод в модели?
Wit
@Wit - можно добавить .order()в цепочку методов, как в последнем примере. Вы об этом спрашиваете?
Мэтт Сандерс
Я прошу прощения. Я не могу вспомнить, чего пытался достичь.
Wit
1
Пример has_many через полиморфизм очень полезен!
Виджай
7

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

class Student < ActiveRecord::Base
  belongs_to :class

  default_scope :order => 'name'

end

Тогда, если вы позвоните

@students = @class.students

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

ниткодер
источник
В Rails 4 это не соответствует требованиям. См. Это решение для правильного синтаксиса Rails 4: stackoverflow.com/questions/18506038/rails-4-default-scope
Кис Бриггс,
0

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

class Article < ActiveRecord::Base 
  has_many :comments, -> { order(created_at: :desc) }, dependent: :destroy
end
Макс Л.
источник