Ler blog em Português

Using UUID with PostgreSQL and ActiveRecord

Read in 3 minutes

PostgreSQL provides lots of column types and ActiveRecord maps some of them. One column type that I’m using more and more is uuid.

The UUID has different implementations, but the most used version is v4. It generates random strings following the xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx format, where:

Let’s generate some identifiers using the uuidgen command. Note how they follow the specification I just mentioned.


You can generate uuidv4 in Ruby too. All you have to do is using the SecureRandom.uuid method, available as part of the standard library.

require 'securerandom'
#=> ff039085-5e16-4d74-9039-4225533e1f35

You can also generate uuidv4 using PostgreSQL. First, you have to activate the uuid-ossp extension.


Then you can generate identifiers using the uuid_generate_v4() function or define uuidv4 columns.

SELECT uuid_generate_v4();
-- cfe48d4f-246f-4f24-85b5-fb1d1a8a520e

  id uuid PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
  name text NOT NULL

INSERT INTO users (name) VALUES ('John Doe');
-- SELECT * FROM users;
--                  id                  |   name
-- -------------------------------------+----------
-- e84f4f0e-2127-4e66-a0be-1463141ae7ae | John Doe

Using uuidv4 as your record identifier has some advantages. The first one isn’t obvious, but you can decentralize the id generation. Let’s say you have a client-side API consumer. Instead of waiting for your server to respond with success or error, you can assume that everything worked and asynchronously push local records to the server API.

Another advantage is that you won’t give tips about how many records your system has. It may seem a silly advantage, but hiding this info is important for some customers. It can also avoid leaking information about your stocks, as Twitter did today.

Implementing UUID in a Rails application

ActiveRecord’s PostgreSQL adapter has built-in support for uuid columns. All you have to do is using the uuid method.

enable_extension 'uuid-ossp'

create_table :users do |t|
  t.uuid :api_key, null: false, default: 'uuid_generate_v4()'

You can also define your primary keys as uuid columns.

create_table :users, id: :uuid do |t|
  # ...

Finally, defining your association column is as simple as setting the :type option.

create_table :posts, id: :uuid do |t|
  t.references :user, type: :uuid, null: false, index: true
  # or
  t.belongs_to :user, type: :uuid, null: false, index: true

The same concept applies to the add_reference method.

add_reference :posts, :users, type: :uuid, null: false, index: true

It’s unlikely that you have duplicated ids. But if that happen, an exception will be raised, since primary keys have an unique constraint.


Using uuid comes with some drawbacks. Some methods, like ActiveRecord::Base.first and ActiveRecord::Base.last won’t work as expected. ActiveRecords expects a sequential identifier, as you can see below.


If you need to use these methods, make sure you sort your results using the created_at column or a sequential column (you can use a bigserial column for this).

User.order(created_at: :asc).first
#=> SELECT * FROM users ORDER created_at ASC LIMIT 1

There’s more. The ActiveRecord::Base.find_in_batches and ActiveRecord::Base.find_each won’t work at all, since they ignore sorting scopes. If you need this functionality, you’ll have to implement it on your own.

Avoiding the boring parts

Setting the primary key and association column types every time is too error prone. To avoid doing this manually, I created a gem that overrides the default methods, adding the column type for you. All you have to do is adding the following line to your Gemfile and you’re done. No more id: :uuid or type: :uuid.

gem 'ar-uuid'

Wrapping up

Even with these ActiveRecord inconsistencies, you can easily use uuid as primary keys. Implementing a smarter behavior for ActiveRecord::Base.first, ActiveRecord::Base.last, ActiveRecord::Base.find_each and ActiveRecord::Base.find_in_batches wouldn’t be too hard; maybe checking the primary column type so that ActiveRecord could switch the sorting to a bigserial column or even created_at.

Do you use uuid as your primary keys? Do you other motivations for doing it so? Leave your comment!