Переопределите ActiveRecord :: Relation :: CollectionProxy только для конкретной модели - PullRequest
0 голосов
/ 12 апреля 2019

В настоящее время я пытаюсь минимизировать количество запросов SQL в приложении rails. И использовал область для фильтрации записей и обнаружил, что в моем журнале сервера область запускает SQL-запрос, хотя он показывает CACHE (0,00 мс), даже если он фильтрует запись, используя атрибуты записи. (например, фильтрация с использованием obj.status)

Я пытался использовать метод класса в модели, опять же, когда я звоню

def pdf_files
  all.select(&:pdf?)
end

прицелы,

scope :pdf_files, -> { all.select(&:pdf?) }

запускает запрос sql.

Я попытался переопределить ActiveRecord :: Associations :: CollectionProxy, чтобы включить этот метод, например,

class ActiveRecord::Associations::CollectionProxy
  def pdf_files
    to_a.select(&:pdf?)
  end
end

сработало , но этот метод также можно вызвать из другой модели.

Я пытался использовать блоки, чтобы расширить ваши ассоциации с помощью дополнительных методов, например.

has_many :files, do
  def pdf_files
    to_a.select(&:pdf?)
  end
end

У меня работает , он выбирает файлы без какого-либо SQL-запроса.

Но дело в том. Мне нужны эти методы, доступные только для файловой модели, так как эти методы требуются для всех других моделей, связанных с файловой моделью, и я не хочу нарушать соглашение по рельсам, например, @email.files.pdf.

Также обнаружил, что в консоли рельсов, когда я набираю File :: ProxyCollection, это дает мне ActiveRecord :: Associations :: CollectionProxy. Но не знаю, как добавить к нему метод.

Я хочу иметь возможность звонить @email.files.pdf ?, @ email.files.doc? , чтобы отфильтровать результаты, не выполняя вообще никакого запроса, даже если он вызывает CACHE SQL Query .

Модель Определения

class TestFile < ApplicationRecord
  belongs_to :fileable, polymorphic: true, optional: true
  scope :document_files, -> { all.select(&:document?) }
  scope :ebook_files, -> { all.select(&:ebook?) }

  enum file_type: [:document, :ebook, :paper, :article, :picture]
end

class TestEmail < ApplicationRecord
  has_many :test_files, as: :fileable
end

class TestPost < ApplicationRecord
  has_many :test_files, as: :fileable
end

в консоли

email = TestEmail.includes(:test_files).first

document_files = email.test_files.document_files

Запрос, выполненный для области действия document_files: enter image description here

Но если я сделаю что-то вроде,

class TestFile < ApplicationRecord
  belongs_to :fileable, polymorphic: true, optional: true

  enum file_type: [:document, :ebook, :paper, :article, :picture]
end

class TestEmail < ApplicationRecord
  has_many :test_files, as: :fileable do
    def document_files
      to_a.select(&:document?)
    end
  end
end

class TestPost < ApplicationRecord
  has_many :test_files, as: :fileable do
    def document_files
      to_a.select(&:document?)
    end
  end
end

результат загружается без запроса enter image description here

1 Ответ

1 голос
/ 13 апреля 2019

Итак, у вас есть полиморфная ассоциация, и вы хотите получить связанные данные с минимально возможным количеством запросов, верно?

Что касается меня, то сама задача («минимизировать количество запросов») имеет смысл только с разумными ограничениями. Это всегда хорошая вещь, чтобы избавиться от N + 1, но это не обязательно хорошая идея, чтобы попытаться сделать все вещи в 1 запросе или что-то вроде этого - сложный запрос может быть медленнее, чем несколько последовательных тривиальных. Кроме того, сложные сложные запросы, как правило, подвержены ошибкам.

Теперь вернемся к вашему коду. Прежде всего, эта идея

scope :document_files, -> { all.select(&:document?) }

выглядит довольно плохо для меня по двум причинам:

  1. все загружается в память и в случае большой таблицы это больно. Иногда это необходимый компромисс, но не в этом случае (вам не нужна тяжелая обработка и т. Д. - только фильтрация данных)

  2. этот «охват» не является обычным - при вызове он дает Array вместо отношения AR, поэтому он не может быть прикован цепью, как это делают правильные области действия AR и т. Д. Это просто вводящий в заблуждение код, который пахнет. Просто попробуйте что-нибудь. как TestFile.document_files.where(<some_extra_conditions>), и вы получите NoMethodError - вероятно, не тот результат, который можно ожидать от простого и понятного кода ...

Использование расширений ассоциации также бесполезно в вашем случае - оно делает ваш код более загадочным, но на самом деле не дает никаких преимуществ. Вы ошибаетесь, полагая, что это работает каким-то особым образом - фактически, оно выполняет ту же работу, что и если вы явно вызываете to_a.select... на прокси-сервере ассоциации.

Я бы предложил что-то более простое и более идиоматическое:

class TestFile < ApplicationRecord
  belongs_to :fileable, polymorphic: true, optional: true
  scope :document_files, -> { where(file_type: :document) }
  scope :ebook_files, -> { where(file_type: :ebook) }

  enum file_type: [:document, :ebook, :paper, :article, :picture]
end

...

Затем, при необходимости, используя предварительную загрузку, вы можете получить небольшое количество запросов (без N + 1).

Хотите оптимизировать это еще больше? Ну, есть много способов сделать это, используя более низкоуровневый API: например, вы можете использовать connection#select_all с произвольно сложным запросом, а затем вручную создать необходимые записи из ActiveRecord::Result без дополнительных запросов ... Но AR - это довольно самоуверенный фреймворк ORM, и если вы собираетесь «бороться» с ним, вы, скорее всего, в итоге получите довольно грязный и не поддерживаемый код.

Если вам действительно нужно что-то более гибкое, я бы предложил попробовать другие варианты (продолжение, ROM) ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...