Home > Software design >  Can Rails Active Storage map to a pre-existing Images table in the database?
Can Rails Active Storage map to a pre-existing Images table in the database?

Time:10-25

I'm working on a legacy Rails app, recently upgraded to Rails 5.2. It already has a roll-your-own Image implementation with uploads, and there are millions of images already in the DB. The Image model belongs_to other models; each of them either has_many :images or has_one :image.

Part of the implementation is that there's a subclass of Image, Image::Url, that gets the image's URL (on AWS), based on the size requested, of the various sizes already generated by ImageMagick.

So I'm considering whether Active Storage (which is not installed on this Rails app) could work with this existing setup. All the tutorials and explainers I've found discuss installing Active Storage on a new Rails app, or using it to add attachments to existing models. That's not my situation -- I already have an Image model relating to a dozen other models, and they already know what their "attachments" i.e. Image relations, are.

My question is, can Active Storage somehow make use of that existing Image table and its relations -- or is Active Storage more properly understood as an alternative to this roll-your-own setup, that cannot integrate with it.

There is an SO question about "Rails Active Storage without model" that seems to imply that a mapping between Active Storage and a model can occur. What i don't understand is the relationship betwen Active Storage and an existing Image model. As far as I understand, it doesn't make sense that the Image model would has_one_attached or has_many_attached (as a User or Product would have attachments) - it IS already a model of the attachment itself. Or am I getting that wrong?

CodePudding user response:

The heart of ActiveStorage is really three tables (and models) which correspond somewhat to your images table:

class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    # Use Active Record's configured type for primary and foreign keys
    primary_key_type, foreign_key_type = primary_and_foreign_key_types

    create_table :active_storage_blobs, id: primary_key_type do |t|
      t.string   :key,          null: false
      t.string   :filename,     null: false
      t.string   :content_type
      t.text     :metadata
      t.string   :service_name, null: false
      t.bigint   :byte_size,    null: false
      t.string   :checksum,     null: false

      if connection.supports_datetime_with_precision?
        t.datetime :created_at, precision: 6, null: false
      else
        t.datetime :created_at, null: false
      end

      t.index [ :key ], unique: true
    end

    create_table :active_storage_attachments, id: primary_key_type do |t|
      t.string     :name,     null: false
      t.references :record,   null: false, polymorphic: true, index: false, type: foreign_key_type
      t.references :blob,     null: false, type: foreign_key_type

      if connection.supports_datetime_with_precision?
        t.datetime :created_at, precision: 6, null: false
      else
        t.datetime :created_at, null: false
      end

      t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
      t.foreign_key :active_storage_blobs, column: :blob_id
    end

    create_table :active_storage_variant_records, id: primary_key_type do |t|
      t.belongs_to :blob, null: false, index: false, type: foreign_key_type
      t.string :variation_digest, null: false

      t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
      t.foreign_key :active_storage_blobs, column: :blob_id
    end
  end

  private
    def primary_and_foreign_key_types
      config = Rails.configuration.generators
      setting = config.options[config.orm][:primary_key_type]
      primary_key_type = setting || :primary_key
      foreign_key_type = setting || :bigint
      [primary_key_type, foreign_key_type]
    end
end

As you can see from the migration it uses the active_storage_blobs to store the actual information about the file that is stored. A blob can also have multiple variants.

active_storage_attachments joins blobs with resources (the model attaching the attachment) through a polymorphic assocation. This lets you add has_one_attached/has_many_attached to any model in your application without adding any additional database columns or tables.

So I'm considering whether Active Storage (which is not installed on this Rails app) could work with this existing setup.

Lets put it this way - you should not expect that you can just plug and play your legacy data into ActiveStorage. Its an extremely opinionated peice of software thats mainly designed around the goal of being able to plugged into a any number of models with a minimum of configuration.

ActiveStorage can probally work fine in tandem with your existing setup (replacing it for new records) but replacing the legacy code with AS will most likely entail some heavy data migration and you'll also need a pretty good understanding of how AS works.

What i don't understand is the relationship betwen Active Storage and an existing Image model.

Thats because there is none really. ActiveSupport::Attachment and ActiveSupport::Blob serve the same role for all the models in your Rails app which have attachments. Its not designed with legacy support in mind.

  • Related