title: Active Storage with Single Table Inheritance and different attachables (STI) #43780
body: <p>I'm using ActiveStorage in a solution I'm building to create documents with a block builder like interface, but I'm running into the N+1 query problem when using STI in combination with active storage and different attachment names.</p>
  <p>Given a ContentBlock which is polymorphic to a contentable</p>
  <pre><code>class ContentBlock &lt; ApplicationRecord
     belongs_to :contentable, polymorphic: true
  end
  </code></pre>
  <p>A document which has_many of these different kind of ContentBlock.</p>
  <pre><code>class Document
    has_many :content_blocks, -&gt; { order(position: :asc) }, as: :contentable, dependent: :destroy
  end
  </code></pre>
  <p>A subset of content blocks as an example: ImageBlock, ProductBlock each with one or many different names for an attached file.</p>
  <pre><code>class ImageBlock &lt; ContentBlock
   has_one_attached :image
  end
  
  class ProductBlock &lt; ContentBlock
   has_one_attached :product_image
  end
  </code></pre>
  <p>Querying this Document and all it's associated ContentBlock records is easy by providing includes(:content_blocks).</p>
  <p>The problem starts when needing information about the ActiveStorage::Attachment records included inside of every subset of ContentBlock</p>
  <p><strong>with_attached_image</strong> works on ImageBlock but not on ProductBlock</p>
  <p><strong>with_attached_product_image</strong> works on ProductBlock but not on ImageBlock</p>
  <p>I can't seem to figure out a way to load all of the associated ActiveStorage::Attachment &amp; ActiveStorage::Blob records without bumping into the N+1 query.</p>
  <p>Am I tackling this problem the wrong way? Are there other ways?</p>
  <p><strong>Solution:</strong></p>
  <p>As discussed before it was impossible to solve the N+1 query on a type of content block based relationship.</p>
  <p>The easiest and most performant solution I found was to declare all my possible relations a derived <code>Contentblock</code> could have on the <code>ContentBlock</code> class itself.</p>
  <p>I then declared the optimised default scopes on each individual block.</p>
  <pre><code>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
  </code></pre>
  <p>The downside of this is that you might overfetch data in some cases. A <code>TextBlock</code> will also do a join to <code>active_storage_attachments</code> even if it doesn't have an image attached.</p>
  <p>The upside is that the N+1 query is gone for good with the tradeoff of having some records overfetch which in my use case is perfectly fine.</p>