Rails - сортировка по данным таблицы соединений - PullRequest
26 голосов
/ 08 февраля 2012

У меня есть проект 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

В таблице «доступность» в базе данных есть дополнительный столбец данных «цена»

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

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

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

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

Ответы [ 6 ]

31 голосов
/ 06 декабря 2015

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

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

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

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

21 голосов
/ 09 февраля 2012

Разобрался с помощью этого связанного поста .

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

Наличие

default_scope :order => "price ASC"

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

Главная

default_scope :include => :availabilities, :order => "availabilities.price ASC"
10 голосов
/ 18 июля 2016

@ 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) будет загружать только те дома, которые связаны по крайней мере с одной доступностью.

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

5 голосов
/ 18 июня 2018

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

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

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

scope :ordered, -> {
  includes(:availabilities).order(Arel.sql('availabilities.price'))
}
3 голосов
/ 11 июля 2018

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

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)
}
0 голосов
/ 14 января 2019

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

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

Привет

...