【问题标题】:Active Storage with Single Table Inheritance and different attachables (STI) #43780具有单表继承和不同附件的活动存储 (STI) #43780
【发布时间】: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


    【解决方案1】:

    您可以尝试将has_one_attached 移动到ContentBlock

    class ContentBlock < ApplicationRecord
      belongs_to :contentable, polymorphic: true
      has_one_attached :media
    
      class << self
        def media_alias(name)
          alias_method name, :media
          alias_method "#{name}=".to_sym, :media=
        end
      end
    end
    
    class ImageBlock < ContentBlock
      media_alias :image
    end
    
    class ProductBlock < ContentBlock
      media_alias :product_image
    end
    

    现在您可以使用 with_attached_media 来预加载附件,并且仍然可以像以前一样访问ImageBlock#imageProductBlock#product_image

    【讨论】:

    • 这部分是我想要的!谢谢您的帮助!我已经把我的解决方案写在了上面。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多