
Security News
Risky Biz Podcast: Making Reachability Analysis Work in Real-World Codebases
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
= ActiveID: binary UUIDs for ActiveRecord
// Document setup :toc: :toc-placement!: :source-language: ruby :source-highlighter: pygments :pygments-style: native :pygments-linenums-mode: inline
// Admonition captions in GitHub (here Emoji) // See: https://github.com/ikatyang/emoji-cheat-sheet/blob/master/README.md ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: :important-caption: :heavy_exclamation_mark: :caution-caption: :fire: :warning-caption: :warning: endif::[]
// Links :dce_uuids: https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01 :gem_original: https://rubygems.org/gems/activeid :gem_uuidtools: https://github.com/sporkmonger/uuidtools :maria_jira_uuid_func: https://jira.mariadb.org/browse/MDEV-15854 :mit_lic: https://opensource.org/licenses/MIT :mysql_uuid: https://mysqlserverteam.com/mysql-8-0-uuid-support/ :examples: https://github.com/riboseinc/activeid/tree/master/examples :percona_blog: https://www.percona.com/blog/2014/12/19/store-uuid-optimized-way/ :rails_api_type_register: https://api.rubyonrails.org/classes/ActiveRecord/Type.html#method-c-register :rfc_uuids: https://tools.ietf.org/html/rfc4122 :ribose: https://www.ribose.com :xkcd_comic: https://xkcd.com/927/
// Badges ifdef::env-github[] image:https://img.shields.io/travis/riboseinc/activeid/master.svg[ Build Status, link="https://travis-ci.org/riboseinc/activeid/branches"] image:https://img.shields.io/codecov/c/github/riboseinc/activeid/master.svg[ Test Coverage, link="https://codecov.io/gh/riboseinc/activeid"] image:https://img.shields.io/codeclimate/maintainability/riboseinc/activeid.svg[ "Code Climate", link="https://codeclimate.com/github/riboseinc/activeid"] endif::[]
A modern, performant and database-agnostic solution for storing UUIDs in ActiveRecord 5.0+, without any obligatory monkey patches.
toc::[]
== Rationale
If you search for "uuid
" in RubyGems, you'll get
https://rubygems.org/search?utf8=%E2%9C%93&query=uuid[142 results] (as of
January 2019)… Most are outdated, all are far from our needs. Yes,
{xkcd_comic}[we think we need another one].
NOTE: ActiveID has evolved from a popular, but no longer maintained {gem_original}[ActiveID] gem, and its forks. From 2018 the gem was entirely rewritten to support newer Rails releases, and was finally detached as a fork in 2020 to prevent confusion from users. We thank Nate for bringing ActiveID to life!
=== Storing UUIDs as binaries in MySQL and SQLite3
UUIDs are 16 bytes long, however their human-readable string representation takes 36 characters. As a consequence, storing UUIDs in a human-readable format is space inefficient. What is worse, whereas table row size is seldom a big concern, size of table indices is significant -- the bigger part of given index fits in RAM, the faster it works. And UUID columns are commonly indexed…
This gem brings an easy-to-use ability to efficiently store UUIDs in databases which do not provide a dedicated UUID data type (i.e. MySQL, MariaDB, SQLite3, etc.).
=== Database-agnosticism
This gem provides a uniform API for storing UUIDs in database, be it MariaDB, MySQL, SQLite3 (in binary or string format), or PostgreSQL (native data type). This is especially important when using it as a dependency of another gem.
=== Monkey patching is optional
No core feature relies on Rails monkey patching. Monkey patches can interfere with other gems, and lead to issues. Nevertheless, some convenient features (currently, migration methods only) are provided via monkey patching. Enabling them is entirely optional, and their absence can be workarounded easily.
=== Strings are not perfect for UUIDs
Although UUIDs are commonly represented as strings, it is beneficial to introduce a dedicated class for following reasons:
It is somewhat similar case to URIs, which also can be represented as plain
strings, but having a dedicated URI
class is quite convenient.
ActiveID uses UUID
class from {gem_uuidtools}[UUIDTools] gem to represent
UUIDs.
== Usage
=== Installation
Depending on you want to apply monkey patches or not, require either
activeid/all
(with monkey patches) or activeid
(without them).
For example, if you are using Gemfile
:
gem "activeid" # without monkey patches
Depending on your needs, you can also pick monkey patches selectively -- just
take a look at the contents of lib/activeid/all.rb
. However, currently
it is not very useful, as there is very little to choose from.
=== Adding UUIDs to models
ActiveID relies on ActiveRecord's attributes API. Two attribute types are
defined: StringUUID
and BinaryUUID
.
StringUUID serializes UUIDs as 36 characters long strings. It is compatible
with textual SQL types, e.g. VARCHAR(36)
, and more importantly, with
PostgreSQL-specific UUID
type.
BinaryUUID serializes UUIDs as 16 bytes long binaries, which can be stored
in binary columns, e.g. BLOB(16)
in SQLite3 or VARBINARY(16)
in MySQL.
However, it is not compatible with PostgreSQL at all due to syntax differences.
See "<>" section for a brief
explanation of pros and cons of both approaches.
Whichever attribute type you prefer to use, an ActiveID::Model
module must
be included in model.
For example, following example model stores two UUID attributes: id
,
and thread_id
as binaries.
=== Database migrations
A convenience #uuid
method is added via monkey patching to Active Record's
Table
and TableDefinition
classes.
VARBINARY(16)
column.BLOB(16)
column.::ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnMethods:uuid
, which
stands for a UUID
column.If you want to use UUID column in your primary key, pass :id => false
option
to create_table
method and :primary_key => true
to column definition.
For example:
Alternatively, if monkey patches are disabled, #uuid
method can be substituted
with #binary
in MySQL and SQLite3 adapters. Following snippet is equivalent
to the above one in these two adapters. Please note :limit => 16
, which is
passed as an option.
=== Registering UUID types in Active Record's type registry
For convenience, Active UUID types can be added to Active Record's type registry. Then you can reference them in your models with a symbol. See {rails_api_type_register}[Rails API docs] for detailed information.
For example, following will register ActiveID::Type::BinaryUUID
at :uuid
symbol for all adapters except for PostgreSQL, in which this symbol is already
taken:
With above set, only symbol needs to be specified in attribute declaration, as in following example:
It is also possible to override :uuid
in PostgreSQL adapter:
=== Using UUIDs as primary keys
When model's primary key is a UUID, Active UUID automatically generates its value as a version 1, 4, or 5 UUID:
UUIDs of all versions can be explicitly assigned to attributes.
==== Random primary keys (version 4 UUIDs)
If model's primary key is a UUID, a version 4 UUID is generated by default. For example:
=== Time-based primary keys (version 1 UUIDs)
They are enabled for model's primary key with #uuid_generator
method.
For example:
=== Name-based primary keys a.k.a. natural keys (version 5 UUIDs)
They are enabled for model's primary key by passing attribute names to
#natural_key
method, and namespace to #uuid_namespace
method. The latter
method accepts only UUIDs, either in string format, or a UUIDTools::UUID
object. If #uuid_namespace
method is omitted, then ISO OID namespace is used.
In following example, a natural key in a6908e1e-5493-4c55-a11d-cd8445654de6
namespace will be build of values of author_id
, and title
attributes.
== Choosing between string and binary serialization
ActiveID allows you to choose between two ways of UUID serialization: as 36 characters long string, or as 16 bytes long binary.
In PostgreSQL, the answer is easy: you should always choose string
serialization. It perfectly works with native UUID
data type, which is
a non-standard feature of PostgreSQL. It also works with textual data types
(i.e. VARCHAR
, TEXT
, etc.), but a UUID
type seems to be a better choice
for performance reasons. Because of special syntax requirements in PostgreSQL,
it does not work with binary types (i.e. BYTEA
), however it seems to be
a neglect-able issue, as UUID
type is more suitable. Please open an issue
if you disagree.
In other RDBSs, either human-readability, or performance must be sacrificed.
With binary serialization, UUIDs are stored in a space-efficient way as 16 bytes long binaries. This is especially beneficial when column is indexed, which is a very common case. Smaller value size means that a bigger piece of index can be kept in RAM, which often leads to a significant performance boost. The downside is that this representation is difficult to read for humans, who access serialized values outside Rails (e.g. in a database console, or in database logs). See also an excellent article "link:{percona_blog}[Store UUID in an optimized way]" in Percona blog for more information about storing UUIDs as binaries.
With string serialization, UUIDs are stored as 36 characters long strings, which
consist only of lowercase hexadecimal digits, and dashes
(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
). They are easy to read for humans, but
may hamper performance of indices, especially in case of large tables.
=== Reading binary UUIDs in a database console
MySQL features a {mysql_uuid}[BIN_TO_UUID()
] function, which converts binary
UUIDs to their human-readable string representation. There is
{maria_jira_uuid_func}[a feature request] to add a similar feature to MariaDB.
== Contributing
First, thank you for contributing! We love pull requests from everyone. By participating in this project, you hereby grant https://www.ribose.com[Ribose Inc.] the right to grant or transfer an unlimited number of non exclusive licenses or sub-licenses to third parties, under the copyright covering the contribution to use the contribution by all means.
Here are a few technical guidelines to follow:
== Credits
This gem is developed, maintained and funded by {ribose}[Ribose Inc.]
The {gem_original}[ActiveID] gem which ActiveID was based on has been developed by Nate Murray with notable help of:
== License
The gem is available as open source under the terms of the {mit_lic}[MIT License].
== See also
FAQs
Unknown package
We found that activeid demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
This episode explores the hard problem of reachability analysis, from static analysis limits to handling dynamic languages and massive dependency trees.
Security News
/Research
Malicious Nx npm versions stole secrets and wallet info using AI CLI tools; Socket’s AI scanner detected the supply chain attack and flagged the malware.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.