How to start using UUID in ActiveRecord with PostgreSQL

Author of photo: Christian Haugen.

Although it may be obvious for many developers, there are still some that are not aware of great features that PostgreSQL allows to use with ActiveRecord. This tutorial is intended to reveal UUID type, which we can use in our Rails applications, especially as models’ attributes.

Special Postgre’s data types are available in our databases by enabling so called extensions. According to the documentation:

PostgreSQL is extensible because its operation is catalog-driven.

The catalogs appear to the user as tables like any other.

PostgreSQL stores much more information in its catalogs like data types, functions and access methods.

These tables can be extended by the user.

Traditional database systems can only be extended by changing source code or by loading modules written by the DBMS vendor.

So not only we get a possibility to create our own extensions, but we get a bunch of useful features out of the box as well. Let’s see one of them in action right now.

Setup

You can follow all of presented steps with your brand new Rails application. To create one, for the purpose of this tutorial, you can run:

rails new -T -J -V -S postgres-extensions --database postgresql

We skipped some tests, javascripts, views and sprockets and set our database to PostgreSQL.

UUID

Personally I think that UUID is extremely interesting topic to discuss and Andrzej has already written an excellent article about using this feature.

This is 16-octet / 128 bit type compatible with most common GUID and UUID generators, supporting distributed application design, defined by RFC 4122, ISO/IEC 9834-8:2005. It is represented by 32 lowercase hexadecimal digits, displayed in five groups separated by hyphens, in the form 8-4-4-4-12 for a total of 36 characters (32 alphanumeric characters and four hyphens).

Versions

Although UUID might appear in different versions (MAC address, DCE security, MD5 or SHA-1 hash), the most generators relies on random numbers and produces UUID version 4.

Version 4 UUIDs have the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx where x is any hexadecimal digit and y is one of 8, 9, A, or B.

So the validation regexp may be as follows:

/[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}/

Ruby stdlib

How can we produce it in Ruby?

[ 1 ] ( pry ) main: 0 > require 'securerandom' true [ 2 ] ( pry ) main: 0 > SecureRandom . uuid "624f6dd0-91f2-4026-a684-01924da4be84"

It’s available since Ruby 1.9 and provides UUID version 4 described above, which is derived entirely from random numbers.

Psql

How to use it in SQL code?

postgres = # CREATE EXTENSION "pgcrypto"; CREATE EXTENSION postgres = # SELECT gen_random_uuid(); gen_random_uuid -------------------------------------- f8c9ffd6-a234-4729-bd2a-68379df315fb ( 1 row )

The pgcrypto module provides functions to generate universally unique identifiers (UUIDs) using one of several standard algorithms. There are also functions to produce certain special UUID constants.

Rails finally

Let’s see how can we use it in our applications. To enable this extension in our database through rails, we can use convenient helper method for PostgreSQLAdapter :

rails g migration enable_uuid_extension

class EnableUuidExtension < ActiveRecord :: Migration def change enable_extension 'pgcrypto' end end

We can create our model now:

rails g model book

class CreateBooks < ActiveRecord :: Migration def change create_table :books , id: :uuid do | t | t . string :title t . timestamps end end end # app/models/book.rb class Book < ActiveRecord :: Base ; end

And then we can play with them a little bit:

2.1 . 2 : 001 > Book . create ( 0.1 ms ) BEGIN SQL ( 0.9 ms ) INSERT INTO "books" ( "created_at" , "updated_at" ) VALUES ( $1 , $2 ) RETURNING "id" [[ "created_at" , "2014-10-01 10:30:12.152568" ], [ "updated_at" , "2014-10-01 10:30:12.152568" ]] ( 0.4 ms ) COMMIT => #<Book id: "adf5efad-7c72-4e3f-9b1a-922fdbf6ebdf", title: nil, created_at: "2014-10-01 10:30:12", updated_at: "2014-10-01 10:30:12"> 2.1 . 2 : 002 > _ . id => "adf5efad-7c72-4e3f-9b1a-922fdbf6ebdf"

So we created our book with auto-generated id as a UUID, great!

But what if I need just another field of uuid type? We can do it too.

rails g migration add_uuid_to_books uuid:uuid

class AddUuidToBooks < ActiveRecord :: Migration def change add_column :books , :uuid , :uuid , default: 'uuid_generate_v4()' end end

After migration we have:

2.1 . 2 : 001 > Book . create ( 0.1 ms ) BEGIN SQL ( 0.2 ms ) INSERT INTO "books" ( "created_at" , "updated_at" ) VALUES ( $1 , $2 ) RETURNING "id" [[ "created_at" , "2014-10-01 10:39:19.646211" ], [ "updated_at" , "2014-10-01 10:39:19.646211" ]] ( 0.9 ms ) COMMIT => #<Book id: "e15b0c03-3ff0-46c2-99b2-76406da80b3a", title: nil, created_at: "2014-10-01 10:39:19", updated_at: "2014-10-01 10:39:19", uuid: nil> 2.1 . 2 : 002 > Book . first Book Load ( 0.4 ms ) SELECT "books" . * FROM "books" ORDER BY "books" . "id" ASC LIMIT 1 => #<Book id: "e15b0c03-3ff0-46c2-99b2-76406da80b3a", title: nil, created_at: "2014-10-01 10:39:19", updated_at: "2014-10-01 10:39:19", uuid: "0699c100-4c6e-4dc3-b72f-91bac8847304">

And we’re done. Note that UUID is accessible only after retrieving a record from the database (or reloading it in place), not immediately in brand new created object, because we get UUID generated from Postgres and not Rails itself.

Threats and inconveniences

To be honest I should mention about some drawbacks right now. Maybe they are not crucial, but it’s worth to be aware that they exists.

Some inconvenience may be referencing with a foreign key to associated model. We can’t just add reference column like:

rails g migration AddAuthorRefToBook

because it produces:

class AddAuthorRefToBook < ActiveRecord :: Migration def change add_reference :books , :author , index: true end end

Which may seems OK at the first sight, but it’s actually a little bit tricky. We have the following models:

class Book < ActiveRecord :: Base belongs_to :author end class Author < ActiveRecord :: Base has_many :books end

and we are trying to create an association:

2.1.2 :001 > Author.create.books.create ( 0.1ms ) BEGIN SQL ( 0.8ms ) INSERT INTO "authors" DEFAULT VALUES RETURNING "id" ( 1.0ms ) COMMIT ( 0.1ms ) BEGIN SQL ( 0.3ms ) INSERT INTO "books" ( "author_id" ) VALUES ( $1 ) RETURNING "id" [[ "author_id" , 49624675]] ( 0.3ms ) COMMIT => #<Book id: "38c66078-9e03-45dc-8b78-408a1a41b55c", title: nil, uuid: nil, author_id: 49624675> 2.1.2 :002 > Book.first Book Load ( 0.8ms ) SELECT "books" . * FROM "books" ORDER BY "books" . "id" ASC LIMIT 1 => #<Book id: "38c66078-9e03-45dc-8b78-408a1a41b55c", title: nil, uuid: "3caa9344-c7e3-4a9e-abb4-e44d1b857d25", author_id: 49624675> 2.1.2 :003 > Author.first Author Load ( 0.4ms ) SELECT "authors" . * FROM "authors" ORDER BY "authors" . "id" ASC LIMIT 1 => #<Author id: "49624675-3386-4423-8cb6-70916972fe34", name: nil>

See what happened? If not, take a look:

2.1.2 :004 > Book.first.author Book Load ( 0.3ms ) SELECT "books" . * FROM "books" ORDER BY "books" . "id" ASC LIMIT 1 Author Load ( 1.1ms ) SELECT "authors" . * FROM "authors" WHERE "authors" . "id" = $1 LIMIT 1 [[ "id" , 49624675]] PG::InvalidTextRepresentation: ERROR: invalid input syntax for uuid: "49624675" : SELECT "authors" . * FROM "authors" WHERE "authors" . "id" = $1 LIMIT 1 ActiveRecord::StatementInvalid: PG::InvalidTextRepresentation: ERROR: invalid input syntax for uuid: "49624675" : SELECT "authors" . * FROM "authors" WHERE "authors" . "id" = $1 LIMIT 1

What is wrong? add_reference associates model by default integer ID, which is not present in our database. Here’s created schema:

create_table "books" , id: :uuid , default: "uuid_generate_v4()" , force: true do | t | t . string "title" t . uuid "uuid" , default: "uuid_generate_v4()" t . integer "author_id" end

Instead we should have a string field for referencing UUID, so any time we connect two tables, we can make a proper association. Fortunately it’s a small change:

class AddAuthorRefToBook < ActiveRecord :: Migration def change add_column :books , :author_id , :uuid end end

which creates:

create_table "books" , id: :uuid , default: "uuid_generate_v4()" , force: true do | t | t . string "title" t . uuid "uuid" , default: "uuid_generate_v4()" t . uuid "author_id" end

and that is what we’re talking about.

2.1.2 :001 > Author.create.books.create.author ( 0.1ms ) BEGIN SQL ( 1.0ms ) INSERT INTO "authors" DEFAULT VALUES RETURNING "id" ( 0.4ms ) COMMIT ( 0.1ms ) BEGIN SQL ( 0.5ms ) INSERT INTO "books" ( "author_id" ) VALUES ( $1 ) RETURNING "id" [[ "author_id" , "e6ebe66f-3c6e-4a1f-ae67-2c5ddfce46f7" ]] ( 0.2ms ) COMMIT => #<Author id: "e6ebe66f-3c6e-4a1f-ae67-2c5ddfce46f7", name: nil>

How unique is universally unique identifier?

While the UUIDs are not guaranteed to be unique, the probability of a duplicate is extremely low. The UUID is generated using a cryptographically strong pseudo random number generator. There’s very slight chance to get the same result twice. Wikipedia provides some nice explanation of possible duplicates.

Conclusion

PostgreSQL offers many more extensions and types out of the box, that are compatible with Rails in easy way. What might be worth to check out are:

The rest will be covered in further blogposts very soon.

Would you like to continue learning more?

If you enjoyed the article, subscribe to our newsletter so that you are always the first one to get the knowledge that you might find useful in your everyday Rails programmer job.

Content is mostly focused on (but not limited to) Ruby, Rails, Web-development and refactoring Rails applications.

References