Proposal: Overridable primary_key on has_one_attached and has_many_attached

Motivation / Background

My application retrofits a legacy database schema which uses a mixture of integer and UUID primary keys for models. Because the foreign key from ActiveStorage::Blob to a given model must be fixed as it is a polymorphic relationship, this causes problems when attempting to attach blobs to models which do not adhere to the expected foreign key type for their primary key.

The suboptimal workaround that I’ve been doing is to create a join table between the model with the incorrect primary key type and ActiveStorage blobs; however, as usage of ActiveStorage increases its leading to a pattern I really dislike.

The ability to override the primary_key attribute of the has_one :"#{name}_attachment" and has_many :"#{name}_attachments" relationships would make this far simpler.

Detail

Patch: Overridable primary_key on ActiveStorage attachment macros by jonahgeorge · Pull Request #1 · jonahgeorge/rails · GitHub

This Pull Request changes the has_one_attached and has_many_attached macros to support optional attachment_primary_key and attachments_primary_key keyword arguments (respectively) for customizing the primary_key attribute of has_one :"#{name}_attachment" and has_many :"#{name}_attachments" relationships.

Example:

class Application < Rails::Application
    config.generators do |g|
      g.orm(:active_record, primary_key_type: :uuid)
    end
end

# == Schema Information
#
# Table name: users
#
#  id       :uuid       not null, primary key
#  name     :string(50)
#
class User < ApplicationRecord
  has_one_attached :profile_picture
end

# == Schema Information
#
# Table name: posts
#
#  id       :integer       not null, primary key
#  title    :string(50)
#  post_id  :uuid        <-- newly created column for compatibility with ActiveStorage::Blob
#
class Post < ApplicationRecord
  has_many_attached :images, attachments_primary_key: "post_id"
end
User.eager_load(:profile_picture_attachment).first

SELECT 
  "users"."id" AS t0_r0, 
  "users"."name" AS t0_r1, 
  "active_storage_attachments"."id" AS t1_r0, 
  "active_storage_attachments"."name" AS t1_r1, 
  "active_storage_attachments"."record_type" AS t1_r2, 
  "active_storage_attachments"."record_id" AS t1_r3, 
  "active_storage_attachments"."blob_id" AS t1_r4, 
  "active_storage_attachments"."created_at" AS t1_r5 
FROM "users" 
LEFT OUTER JOIN "active_storage_attachments" 
  ON "active_storage_attachments"."record_type" = ? 
  AND "active_storage_attachments"."name" = ? 
  AND "active_storage_attachments"."record_id" = "users"."id" # Existing behavior
ORDER BY "users"."id" ASC 
LIMIT ?
> Post.eager_load(:images_attachments).first

SELECT 
  "posts"."id" AS t0_r0, 
  "posts"."title" AS t0_r1, 
  "posts"."post_id" AS t0_r2, 
  "posts"."user_id" AS t0_r3, 
  "active_storage_attachments"."id" AS t1_r0, 
  "active_storage_attachments"."name" AS t1_r1, 
  "active_storage_attachments"."record_type" AS t1_r2, 
  "active_storage_attachments"."record_id" AS t1_r3, 
  "active_storage_attachments"."blob_id" AS t1_r4, 
  "active_storage_attachments"."created_at" AS t1_r5 
FROM "posts" 
LEFT OUTER JOIN "active_storage_attachments" 
  ON "active_storage_attachments"."record_type" = ? 
  AND "active_storage_attachments"."name" = ? 
  AND "active_storage_attachments"."record_id" = "posts"."post_id" # New column override
WHERE "posts"."id" = ? 
ORDER BY "posts"."id" ASC

  • Is this something that would be acceptable?
  • I’m actively working on getting the Rails testsuite executing locally; however, am concerned that I don’t fully understand the structure of the ActiveStorage testsuite well enough to write a adequate tests for this.