How to create auto incrementing primary key with prefix in RoR application.

Recently, while experimenting with Stripe, I noticed that objects have IDs or identifiers with prefixes. Below, I've listed a few examples. You can find more in this gist: https://gist.github.com/fnky/76f533366f75cf75802c8052b577e2a5
| Prefix | Description | Notes |
| cus_ | Customer ID | Identifier for a Customer object. |
| pk_live_ | Live public key | Public key in a live environment. |
| pk_test_ | Test public key | Public key in a test environment. |
I found this extremely useful. You see the ID, and right away, you can identify the object it belongs to. Here, I found a quote from Stripe's co-founder:
They're randomly generated by our Ruby application code. We use the
ch_-style prefixes because we find it really useful to be able to immediately recognize the type of an ID when looking at logs or stacktraces.
In this article, I will show you how to implement this in a RoR application. We will get ids like: usr_1 urs_2 usr_3. Let's dive into the implementation.
Implementation
First, let's create a migration:
rails g migration create_users email
This will generate our migration file:
class CreateUsers < ActiveRecord::Migration[7.1]
def change
create_table :users do |t|
t.string :email
t.timestamps
end
end
end
By default all tables will be crated with primary_key :id. We want to change this, so we add id: false option
create_table :users, id: false do |t|
t.string :email
t.timestamps
end
Now we need to attach primary key to the table:
create_table :users, id: false do |t|
t.string :id, null: false, primary_key: true, default: -> { "'usr_'||nextval('usr_seq_id'::regclass)::TEXT" }
t.string :email
t.timestamps
end
Let's look closer on the implementation:
"'usr_'||nextval('usr_seq_id'::regclass)::TEXT"
If we don't attach an ID when creating a user, then this code will be called from the default. Here, we call a PostgreSQL sequence that will automatically generate our ID. Before that, we added the prefix usr_. However, we haven't created a sequence yet. So let's do that. Before creating a table, we need to create a sequence. I couldn't find an Active Record function to accomplish this, so we'll use the connection.execute method instead.
This is how it will look like:
connection.execute('CREATE SEQUENCE usr_seq_id')
create_table :users, id: false do |t|
t.string :id, null: false, primary_key: true, default: -> { "'usr_'||nextval('usr_seq_id'::regclass)::TEXT" }
end
connection.execute is not reversible, so we need to change the method to up and down. Also, let's update the sequence with an owner, so the sequence will be deleted automatically when the table is dropped.
class CreateUsers < ActiveRecord::Migration[7.1]
def up
connection.execute('CREATE SEQUENCE usr_seq_id')
create_table :users do |t|
...
end
connection.execute('ALTER SEQUENCE usr_seq_id OWNED BY users.id')
end
def down
drop_table :users
end
end
Now, everything is working. I hope you found this article helpful.



