I'm Randy Burgess, a software manager / developer / instructor / architect based out of Kansas City.

Using ActiveStorage with UUIDs on Rails 5.2 and PostgreSQL 9+

Published:  Saturday, February 3, 2018
Updated:  Monday, February 5, 2018

How to make Rails 5.2.0rc1, ActiveStorage, and UUIDs on PostgreSQL 9+ work together with some small adjustments to the standard setup process.

What's This About?

Most of the ActiveStorage tutorials out there in the blogosphere, which have been posted in anticipation of the Rails 5.2.0rc1 stable launch, show you how to utilize ActiveStorage with the standard sequential model IDs (integers). But what if you want to use UUIDs for the unique keys on your Rails models?

In this short walkthrough, I will show you how to get Rails 5.2.0rc1, ActiveStorage, and UUIDs on PostgreSQL working together.

Installing Rails 5.2.0rc1

I don't need to repeat the same basic setups that have been written before, so I'll recommend you follow the following posts for those instructions:

Bottom line, you need to install rails with rails _5.2.0.rc1_ new app_name and then run bundle install for your gems

Switch from Sqlite to PostgreSQL

Next, tell Rails to utilize PostgreSQL instead of sqlite: Ruby on Rails Switch From Sqlite3 to Postgres

Checking out your Gemfile file:

# Use sqlite3 as the database for Active Record
# gem 'sqlite3'
# Use pg, instead
gem "pg", "0.21.0"

Full page example

Checking out your database.yml file:

development: &default
  adapter: postgresql
  host: localhost
  username: username
  database: five-two_dev
  pool: 5
  timeout: 5000

test:
  <<: *default
  adapter: postgresql
  username: username
  database: five-two_test

Full page example

Changes to ActiveStorage migrations

After you run the rails active_storage:install command, you need to jump in and change a few things on the generated migrations before running them.

You'll need to ensure your PostgreSQL database is ready for UUIDs in the first place. Add these lines to a migration (or the generated one):

enable_extension "uuid-ossp"
enable_extension "pgcrypto"

Also, you may want to reference this post for more about the initial setup of UUIDs in your app: Rails 5.1 + Using a UUID as a primary key in ActiveRecord with PostgreSQL

Full page example

Changes to Tables

Make changes to the initial setup of both active_storage_blobs and active_storage_attachments tables to utilize the uuid datatypes for their model id fields:

create_table :active_storage_blobs, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
  ...
end

create_table :active_storage_attachments, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
  ...
end

Full page example

Do the same for your other models that will have associated ActiveStorage instances. In this case, I'm using a generic Post model:

create_table "posts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
  ...
end

Full page example

Changes to Fields

The above changes simply cover the setup of the tables. Now we'll make changes to the actual t.references fields which will break our UUID approach, by default:

Change your active_storage_attachments table from this:

create_table :active_storage_attachments do |t|
  t.string     :name,     null: false
  t.references :record,   null: false, polymorphic: true, index: false # default, problematic instruction
  t.references :blob,     null: false # default, problematic instruction

  t.datetime :created_at, null: false

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

To this:

create_table :active_storage_attachments, id: :uuid, default: -> { "gen_random_uuid()" } do |t|
  t.string :name,     null: false
  t.uuid :record_id, null: false     # replaces t.references :record
  t.string :record_type, null: false # replaces t.references :record
  t.uuid :blob_id,     null: false   # replaces t.references :blob
  t.datetime :created_at, null: false

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

Full page example

Closing Remarks

Yep, changing the migrations is mostly all there is to it. The biggest issue you're overcoming with these changes is that Rails does not understand utilizing UUIDs via the default generators, and the t.reference helpers and will incorrectly (for our purposes) set up the polymorphic and association IDs as normal, sequential integers.

Thus, the changes above explicitly tell Rails to use UUIDs instead of the traditional IDs for the necessary associations.

Feedback

If you have problems with these instructions, please shoot me an email or create an issue on the source repo and I'll see what's up!

Notes

Other things to check out