#ruby-on-rails #ruby #database #postgresql
Вопрос:
Модель Subscription
has_many
:versions
.
Я пишу запрос, чтобы получить подписки и упорядочить их по соответствующей versions
последней authorized_at
дате, но я не уверен, что запрос может возвращать одну и ту же подписку более одного раза из-за операторов joins
and group
.
Subscription
.joins(:versions)
.group("subscriptions.id, users.id")
.order("MAX(versions.authorized_at) ASC")
С другой стороны, у меня есть метод Subscription
экземпляра current_version
, который возвращает последний авторизованный version
.
подписка.рб
def current_version
versions.authorized.last
end
Я думаю, что ассоциация (вместо метода экземпляра) могла бы помочь мне, чтобы я мог присоединиться к таблицам подписки и версий и упорядочить результаты по дате авторизации связанной таблицы.
Но как мне написать ассоциацию, которая выполняет запрос так же, как это делает метод класса?
Я попытался сделать что-то вроде:
has_one :current_version,
-> { versions.authorized.last },
class_name: "Version", inverse_of: "Subscription"
Но я получаю NameError: undefined local variable or method 'versions' for #<Version::ActiveRecord_Relation
Ответ №1:
Лучшим решением для повышения производительности чтения является добавление отдельного столбца внешнего ключа в таблицу в качестве короткого пути:
add_reference :subscriptions,
:current_version,
foreign_key: {
to_table: :versions
}
class Subscription < ApplicationRecord
has_many :versions,
after_add: :set_current_version
belongs_to :current_version,
class_name: 'Version'
def set_current_version(version)
update_attribute(:current_version_id, version.id)
end
end
При этом используется обратный вызов ассоциации для установки связи, но вы также можете обработать ее с помощью триггера БД или объекта службы.
Если вы действительно хотите использовать has_one
, вам нужно использовать хакерство, такое как подзапрос, оконная функция или последующее соединение:
class Subscription
has_one :current_version,
-> {
where(
id: Version.select(:id)
.group(:subscription_id)
.order("MAX(versions.authorized_at) ASC")
)
},
class_name: 'Version'
end
Комментарии:
1. Спасибо, Макс. Я попробовал has_one, который вы предложили, и запрос работает в консоли, но я попадаю
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "current_version"
в представление
Ответ №2:
Я попробовал приведенный выше ответ Макса, и он работал в консоли (я только попробовал has_one
предложение), но мне не удалось заставить представления работать (я продолжал получать неопределенные ошибки таблицы).
В конце концов я создал область для получения подписки с ее current_version
(последней авторизованной version
) и использовал ее.
scope :with_current_version, -> {
select("subscriptions.*, last_version.authorized_at AS last_version_authorized_at")
.joins("LEFT OUTER JOIN (SELECT DISTINCT ON (subscription_id) * FROM versions
ORDER BY versions.subscription_id,
versions.authorized_at DESC) AS last_version
ON last_version.subscription_id = subscriptions.id")
}
А затем в моем контроллере:
@subscriptions = current_account.subscriptions.with_current_version
.order("last_version_authorized_at ASC")