DEV Community

Renzo Diaz
Renzo Diaz

Posted on • Edited on

How to use ULID as primary key Rails

A short story

Ruby on Rails is a powerful framework it is easy to create APIs and fast website development, one thing that came when I was developing an API was how to change the Integer auto-increment primary key because having a predictable Id can be risky in terms of security if you share it for a client-size application, the user can guess ids and modify the database in the worst of the case, at that moment I had to research some alternatives and I found UUID a non-sequential data type that can be easily be implemented in Rails, so I've tried it and everything was ok until I had to sort ASC and DESC, I realize that the methods Model.first and Model.last doesn't work as expected because Rails by default uses them with the integer sequential id type, so as UUID is non-sequential it generates a random Hash like this 123e4567-e89b-12d3-a456-426614174000 as it doesn't have a sequence Rails can't figure out how to sort the data, then I researched a little bit more to see if there is any other solution and I found that we can use self.implicit_order_column = "created_at" in the Model, with this, those methods work ok it sorts depending on the DateTime created and I was happy. Not for a long time, when another problem came up! When I used seeds to fill a bulk of fake data into the database, all the data had the same DateTime and the sorting failed again, it was the same issue as the initial one. I've decided to look for another alternative and I found ULID, using this I had everything I've needed the sorting works as expected, it isn't predictable and the store in memory is pretty good. I'm still using it and I don't have any issues.

How to implement ULID in Rails?

There is already a gem for ruby add it to your Gemfile

#Gemfile ... gem 'ulid' end 

Then run bundle install to install it. Then update your migration or if you are creating a new one should look like this.

# migration class CreateUsers < ActiveRecord::Migration[6.0] def change # id: false, to not use id int as default create_table :users, id: false do |t| # here we create our id t.binary :id, limit: 16, primary_key: true t.string :given_name t.string :family_name t.string :email, unique: true, index: true t.string :username, unique: true, index: true t.string :password_digest ... t.timestamps end end end 

First, we disable the autogenerated id by putting id: false and create our own id t.binary :id, limit: 16, primary_key: true what we need to do is fill the id before create, we can do it on each Model but my approach was to create a concern to just include it in the application_record.rb file so it will apply for all the models.

# app/models/concenrs/ulid_pk.rb require 'ulid' module UlidPk extend ActiveSupport::Concern included do before_create :set_ulid end def set_ulid self.id = ULID.generate end end 

Then in the application_record.rb include it.

# app/models/concenrs/ulid_pk.rb class ApplicationRecord < ActiveRecord::Base include UlidPk self.abstract_class = true end 

That's it, now it will autogenerate the id before create.

Note

if you need to create an association you should put the type on it.

# migration class CreateEvents < ActiveRecord::Migration[6.0] def change # id: false, to not use id int as default create_table :users, id: false do |t| # here we create our id t.binary :id, limit: 16, primary_key: true t.string :name t.string :description ... # Specify the type: binary t.references :user, type: :binary, foreign_key: true, index: true t.timestamps end end end 

Hope this can help you, I'll try to make it more autogenerated when I get the chance to avoid put type: :binary or id: false manually. If you have any question feel free to reach me out. Cheers!

Buy me a coffee

Top comments (1)

Collapse
 
samuelodan profile image
Samuel O'Daniels

This was helpful. Thanks.