Как я могу превратить метод экземпляра в ассоциацию?

#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")