【发布时间】:2021-12-05 22:53:07
【问题描述】:
我在我正在构建的解决方案中使用 ActiveStorage 来创建具有类似界面的块生成器的文档,但是当将 STI 与活动存储和不同的附件名称结合使用时,我遇到了 N+1 查询问题。
给定一个内容块,它是可内容的多态
class ContentBlock < ApplicationRecord
belongs_to :contentable, polymorphic: true
end
具有_many这些不同类型ContentBlock的文档。
class Document
has_many :content_blocks, -> { order(position: :asc) }, as: :contentable, dependent: :destroy
end
以内容块的子集为例:ImageBlock、ProductBlock,每个附加文件都有一个或多个不同的名称。
class ImageBlock < ContentBlock
has_one_attached :image
end
class ProductBlock < ContentBlock
has_one_attached :product_image
end
通过提供 include(:content_blocks) 可以轻松查询此文档及其所有相关的 ContentBlock 记录。
当需要有关包含在每个 ContentBlock 子集中的 ActiveStorage::Attachment 记录的信息时,问题就开始了
with_attached_image 适用于 ImageBlock 但不适用于 ProductBlock
with_attached_product_image 适用于 ProductBlock 但不适用于 ImageBlock
我似乎无法找到一种方法来加载所有关联的 ActiveStorage::Attachment 和 ActiveStorage::Blob 记录而不碰到 N+1 查询。
我是否以错误的方式解决这个问题?还有其他方法吗?
解决方案:
如前所述,不可能解决基于内容块的关系类型的 N+1 查询。
我发现的最简单和最高效的解决方案是声明派生的Contentblock 可能与ContentBlock 类本身存在的所有可能关系。
然后我在每个单独的块上声明了优化的默认范围。
class ContentBlock
# Relations are defined here to make proper use of includes(:relation)
# ContentBlock::Image
has_one_attached :image
# ContentBlock::Sign
has_many :signatures, as: :signable, dependent: :destroy
DEFAULT_SCOPE = [
image_attachment: :blob,
signatures: Signature::DEFAULT_SCOPE
].freeze
default_scope do
includes(DEFAULT_SCOPE)
end
end
这样做的缺点是在某些情况下您可能会过度获取数据。即使没有附加图像,TextBlock 也会连接到 active_storage_attachments。
好处是 N+1 查询永远消失了,但需要权衡一些记录过度获取,这在我的用例中非常好。
【问题讨论】:
标签: ruby-on-rails polymorphic-associations rails-activestorage