Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 5.2 ActiveStorage with UUIDs on Postgresql

We have our app using uuids are primary keys, on a Postgresql Database. (Standard setup described here).

We integrated ActiveStorage following the process described here. A standard setup using rails active_storage:install and migrated using rails db:migrate.

We have a model & corresponding controller as follows:

# Model
class Message < ApplicationRecord
  has_one_attached :image

  def filename
    image&.attachment&.blob&.filename
  end
end

# Controller
class MessagesController < ApplicationController
  def create
    message = Message.create!(message_params)
    redirect_to message
  end

  private
    def message_params
      params.require(:message).permit(:title, :content, :image)
    end
end

We observed that the first few sets of image were correctly associated with the model instances, but then we used to get random images for model instance, or got no image at all. Every time, we restart the server, we get first few images right, but then it was unpredictable.

Unsure, of what's going wrong, we debugged in rails console:

params[:image]
=> #<ActionDispatch::Http::UploadedFile:0x007fcf2fa97b70 @tempfile=#<Tempfile:/var/folders/dt/05ncjr6s52ggc4bk6fs521qw0000gn/T/RackMultipart20180726-8503-vg36kz.pdf>, @original_filename="sample.pdf", @content_type="application/pdf", @headers="Content-Disposition: form-data; name=\"file\"; filename=\"sample.pdf\"\r\nContent-Type: application/pdf\r\n">

On saving the instance and retrieving file name we got a random file, we uploaded previously.

@message = Message.new(message_params)
@message.filename
=> #<ActiveStorage::Filename:0x007fcf32cfd9e8 @filename="sample.pdf">

@message.save

@message.filename
=> #<ActiveStorage::Filename:0x007f82f2ad4ef0 @filename="OtherSamplePdf.pdf"> 

Looking for an explanation for this weird behaviour, and a possible solution too.

like image 994
anurag Avatar asked Jul 26 '18 05:07

anurag


2 Answers

I'm late to the party hitting this in 2020 but as anurag mentioned this is due to the active_storage_attachments DB table using a bigint for the record_id. I wasn't able to migrate all the models with ActiveStorage attachments to use UUIDs so I needed a way to support both UUIDs and bigints at the same time.

Warning: If you can avoid this (by migrating everything to UUID most likely) then I'd strongly recommend doing that, and I plan on doing that as soon as we have time.

Warnings aside, migrating the active_storage_attachments table to change the record_id column to be text does work. I had to adjust the few places in our app where we were joining against the active_storage_attachments table using record_id to cast the value to text in the join. For example I used the following code when joining to a model that had a UUID ID.

      .joins("
         LEFT OUTER JOIN active_storage_attachments
         ON active_storage_attachments.record_id = documents.id::text
       ")

Hopefully this helps someone else who is stuck in the halfway state where not all ActiveStorage using models are using UUIDs or bigint IDs.

like image 104
Haegin Avatar answered Nov 08 '22 08:11

Haegin


After hours of going line by line in the activestorage source code, and running the same commands

@message = Message.new(message_params)
@message.save

again and again. We got same random results again and again. Then we went through the logs rails printed while attaching the image to message and observed the following:

S3 Storage (363.4ms) Uploaded file to key: KBKeHJARTjnsVjkgSbbii4Bz (checksum: S0GjR1EyvYYbMKh44wqlag==)

ActiveStorage::Blob Create (0.4ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["key", "KBKeHJARTjnsVjkgSbbii4Bz"], ["filename", "sample.pdf"], ["content_type", "application/pdf"], ["metadata", "{\"identified\":true}"], ["byte_size", 3028], ["checksum", "S0GjR1EyvYYbMKh44wqlag=="], ["created_at", "2018-07-26 04:54:33.029769"]]

ActiveStorage::Attachment Create (2.7ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "file"], ["record_type", "Message"], ["record_id", "534736"], ["blob_id", "0"], ["created_at", "2018-07-26 05:04:35.958831"]]

record_id was being set as 534736, instead of an uuid. Here's where we went wrong.

Active storage was expecting integer foreign key to our Message model, and we wanted it to use uuids instead. So we had to fix our migration, to use uuids instead of integer foreign keys.

Solution:

class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    create_table :active_storage_blobs, id: :uuid do |t|
      t.string   :key,        null: false
      t.string   :filename,   null: false
      t.string   :content_type
      t.text     :metadata
      t.bigint   :byte_size,  null: false
      t.string   :checksum,   null: false
      t.datetime :created_at, null: false

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

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

      t.datetime :created_at, null: false

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

Hope this helps, someone facing similar issues. cheers!

like image 41
anurag Avatar answered Nov 08 '22 08:11

anurag