Як я автоматично сортую відносини 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 (правда). Не забудьте використати .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 - ви можете додати .order()до ланцюжка методів, як в останньому прикладі. Це те, про що ви запитуєте?
Метт Сандерс

Вибачте. Я не можу згадати, чого я намагався досягти.
Дотепність

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.