Rails - сортировка по данным таблицы соединений

У меня есть проект RoR в работах. Вот применимые разделы моих моделей.

Главная

has_many :communities, :through => :availabilities
has_many :availabilities, :order => "price ASC"

Основной

has_many :homes, :through => :availabilities
has_many :availabilities

Доступность

belongs_to :home
belongs_to :community

В таблице "availabilities" в базе данных есть дополнительный столбец данных "цена"

Итак, теперь я могу позвонить

@home.availabilities.each do |a|
  a.community.name
  a.price

и получить данные о доступности, упорядоченные по цене, как я хочу. Мой вопрос таков:

Есть ли способ автоматически упорядочить дома с помощью avaliabilities.first.price (first = lower)? Может быть, что-то с default_scope :order?

Ответ 1

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

Там нет ничего плохого с собственной областью, она проще и еще яснее, вы можете сделать это так просто, как:

scope :ordered, -> { includes(:availabilities).order('availabilities.price') }

PS: не забудьте добавить индекс по price; Также посмотрите другие отличные ответы здесь, чтобы выбрать между присоединением/включением.

Ответ 2

Выяснил это с помощью этого связанного сообщения.

Я переместил порядок из модели Home и в модель доступности:

Доступность

default_scope :order => "price ASC"

Затем я загружаю загруженные возможности в модель Home и сортируется по цене:

Главная

default_scope :include => :availabilities, :order => "availabilities.price ASC"

Ответ 3

@ecoologic ответ:

scope :ordered, -> { includes(:availabilities).order('availabilities.price') }

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

С практической точки зрения есть два основных различия:

  1. includes загруженные записи (ы); в этом случае Availability записей. joins не загружают никаких связанных записей. Таким образом, вы должны использовать includes когда вы хотите использовать данные из модели соединения, например, отображать price где-либо. С другой стороны, joins следует использовать, если вы собираетесь использовать данные модели объединения только в запросе, например, в предложениях ORDER BY или WHERE.

  2. includes загружает все записи, а joins загружает только те записи, которые связаны с моделью соединения. Таким образом, в случае OP Home.includes(:availabilities) будут загружать все дома, в то время как Home.joins(:availabilities) будет загружать только те дома, которые связаны по крайней мере с одной доступностью.

Также смотрите этот вопрос.

Ответ 4

В Rails 5. 2+ вы можете получить предупреждение об устаревании при передаче строкового параметра в метод order:

ПРЕДУПРЕЖДЕНИЕ ОБ УСТРАНЕНИИ: Опасный метод запроса (метод, аргументы которого используются в качестве необработанного SQL), вызываемый с неатрибутивным аргументом (ами): "table.column". Аргументы без атрибутов будут запрещены в Rails 6.0. Этот метод не должен вызываться с предоставленными пользователем значениями, такими как параметры запроса или атрибуты модели.

Чтобы решить эту проблему, вы можете использовать Arel.sql():

scope :ordered, -> {
  includes(:availabilities).order(Arel.sql('availabilities.price'))
}

Ответ 5

Еще один способ добиться этого:

scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price]) }

Вы также можете указать направление ASC с

scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].asc) }

DESC:

scope :ordered, -> { includes(:availabilities).order(Availability.arel_table[:price].desc) }

Использование arel_table в модели ActiveRecord позволяет сэкономить на сценарии, когда имя таблицы изменилось (но это случается очень редко).

Обратите внимание, что было бы неплохо добавить main_table#id для детерминированной сортировки.

Итоговая версия будет:

scope :ordered, -> {
  includes(:availabilities).
    order(Availability.arel_table[:price].asc, order(Home.arel_table[:id].asc)
}

Ответ 6

Вы также можете отсортировать связанные таблицы следующим образом (например):

class User
  has_many :posts
end

class Post
  belongs_to :user

  scope :sorted_by_user_and_title, -> { 
    joins(:user).merge(
     User.order(first_name: :asc, last_name: :asc)
    )
    .order(title: :desc)
    # SELECT * FROM 'posts'
    # INNER JOIN 'users' ON 'posts'.'user_id' = 'users'.'id'
    # ORDER BY
    # 'users'.'first_name' ASC, 'users'.'last_name' ASC, 'posts'.'title' DESC;
  }
  scope :sorted_by_title_and_user, -> { 
    order(title: :desc)
    .joins(:user).merge(
     User.order(first_name: :asc, last_name: :asc)
    )
    # SELECT * FROM 'posts'
    # INNER JOIN 'users' ON 'posts'.'user_id' = 'users'.'id'
    # ORDER BY
    # 'posts'.'title' DESC, 'users'.'first_name' ASC, 'users'.'last_name' ASC;
  }
end

С уважением