Switching Scaffolded Lucky Crystal Apps to UUIDs

Stephen DolanJune 23, 2020

Introducing...

I've really been enjoying working with Lucky over the last few weeks, having developed on the web using only Rails for as long as I can remember. The community is fantastically welcoming and helpful, the Lucky framework is artfully crafted and intuitive, and it is fast without trying too hard or making things obscure.

Lucky has a CLI that can be used to generate apps using a very simple lucky init command. If you generate an app with authentication built-in, you get a User model with an ID column that's defaulted to an Int64. Here's a (hopefully future-proof) set of steps to convert over to UUIDs in a scaffolded app!

Add the pgcrypto extension to Postgres

The first action to take is straightforward, but requires a few steps.

We need to add the pgcrypto extension to our database before the user model is created. To do that, we'll rename that migration and insert a new one before it:

mv 00000000000001_create_users.cr 00000000000002_create_users.cr

I also went ahead and manually created the migration for adding the extension:

touch 00000000000001_enable_pgcrypto.cr`

Add this content to that file to create the extension in your database and remove it on rollback:

class EnablePgcrypto::V00000000000001 < Avram::Migrator::Migration::V1
  def migrate
    execute "CREATE EXTENSION IF NOT EXISTS pgcrypto"
  end

  def rollback
    execute "DROP EXTENSION pgcrypto"
  end
end

Tell Lucky to expect UUIDs by default for all model IDs

You can enable this on only specific modules, but if you're planning on using UUIDs for all of your application's records, go ahead and add this to src/models/base_model.cr:

macro default_columns
  primary_key id : UUID
  timestamps
end

You can read more about your options for this step in the Lucky Guides.

Change some types!

Crystal is a typed language, so we need to tell the compiler that everywhere it previously expected a 64-bit integer for a User's ID, it now needs to expect a UUID. We also need to change the code supplying that User ID to supply a UUID instead of an integer.

Rather than running through file-by-file, which may become out of date as the Lucky CLI and framework evolve, here are the two things to search for that you'll need to replace in your generated application:

Int64

The only occurrences of this type in your app should be for typing User.id, and the switch to UUIDs is as simple as replacing Int64 with UUID. I'll provide my changes from the PasswordResets::NewPage as an example.

Before:

class PasswordResets::NewPage < AuthLayout
  needs operation : ResetPassword
  needs user_id : Int64

  ...
end

After:

class PasswordResets::NewPage < AuthLayout
  needs operation : ResetPassword
  needs user_id : UUID

  ...
end

.to_i64

This is the last change we need to make. We've covered converting type expectations in the previous section, and now we'll convert the code that provides the User ID.

The change to make to callers of to_i64 is to strip off to_i64, and pass everything else to UUID.new. I'll provide two examples below.

From the PasswordResets::Create action

Before:

html NewPage, operation: operation, user_id: user_id.to_i64`

After:

html NewPage, operation: operation, user_id: UUID.new(user_id)

From the UserToken model:

Before:

payload["user_id"].to_s.to_i64

After:

UUID.new(payload["user_id"].to_s)

Summary

To convert a generated Lucky app to use UUIDs, we completed these actions:

  1. Add the pgcrypto extension to Postgres
  2. Tell Lucky to use UUIDs by default for model IDs
  3. Rename type delcarations from Int64 -> UUID
  4. Refactor callers to pass UUID.new values instead of String#to_i64 values

For further reading, head over to the Lucky Guides and search UUID in the search bar at the top of the page!