Search code examples
ruby-on-railspostgresqluuidrails-activestorage

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.


Solution

  • 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!