163

I have a problem that is really easily solved with GUIDs.

In particular, for a password reset workflow, I would like to send a GUID token to a user's email and have them reset their password using the token. Since GUIDs are unique, this is pretty secure and saves me emailing people passwords, which is risky.

I noticed there is one uuid gem @ rubyforge but it looks quite old, and it writes stuff to the file system.

Does anyone know of any other gems that can create a globally unique identifier?

I know I can just fall back to:

(0..16).to_a.map{ |a| rand(16).to_s(16) }.join 

But it does not really seem like a proper GUID ...

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • 1
    Using a random string like that would not be quite right; certain bits in the UUID specify variant and version. For a random UUID, you probably want variant 2 (RFC 4122) and version 4, in which case 6 certain bits must be set to the right values. – jtpereyda Apr 05 '13 at 17:06
  • 1
    Yes @dafrazzman is right. Randomly piecing together something that "resembles a UUID" does not guarantee uniqueness. While no UUID is *truly* guaranteed, building one with random numbers is FAR more susceptible to collisions and could not be worthy of the label "UUID". Definitely go with SecureRandom.uuid! – dooleyo Sep 27 '13 at 18:01

11 Answers11

353

As of Ruby 1.9, uuid generation is built-in. Use the SecureRandom.uuid function.

For example:

require 'securerandom'
SecureRandom.uuid # => "96b0a57c-d9ae-453f-b56f-3b154eb10cda"
maerics
  • 151,642
  • 46
  • 269
  • 291
J _
  • 4,035
  • 1
  • 16
  • 8
  • 5
    SecureRandom.uuid generates a random UUID, so it is not guaranteed as unique. If you just want a random string that is probably unique it will be okay to use this. However, if you want something that is guaranteed to be unique you will need to use something that includes the MAC address, timestamp, and et cetera. – Mike Dotterer Oct 01 '12 at 15:58
  • 26
    To save you a bit of lookup, you'll need to require 'securerandom' – Jesse Shieh Dec 12 '12 at 18:41
  • 8
    It's not guaranteed to be unique, but for most practical purposes, it's safe to assume it's unique. See: http://stackoverflow.com/questions/2977593/is-it-safe-to-assume-a-guid-will-always-be-unique – Jesse Shieh Dec 12 '12 at 18:44
  • 1
    if SecureRandom.uuid follow RFC 4122 as it says according to the documentation, doesn't that means it has a timestamp field? Barring concurrency, doesn't that mean unique? – Michael K Madison Jan 08 '16 at 20:33
  • @MichaelKMadison AFAIK Ruby uses the "v4" variant of RFC 4122, which doesn't use a timestamp, so the chance of collision isn't actually nil - but in practice it may as well be – Edd Morgan Mar 28 '16 at 19:02
  • @MikeDotterer, there are reported occasions of matching MAC as well timestamp is also possibly duplicatable in a multitask environment. Chances are low but also random collision is also very low. You can't really ever guarantee uniqueness unless you keep track of all generated entries. But this is worthless in this case IMO. btw `SecureRandom.uuid` works for me on ruby 2.4 – akostadinov Apr 30 '18 at 12:35
  • 1
    @MikeDotterer No UUID generator can guarantee uniqueness. They use different methods to make collision very unlikely but never impossible. – Mecki Jun 30 '19 at 14:46
  • Prior to Ruby 1.9, the equivalent method is `SecureRandom.hex` (although this does not include hyphens) – DaveMongoose Jul 24 '19 at 15:32
  • The chance of a collision (duplicate) is as follows: if you generate 1 billion UUIDs per second, and keep doing so for 85 years straight, that gives a 50% chance of 1 collision. [Source](https://en.wikipedia.org/wiki/Universally_unique_identifier#Collisions). I treat UUIDs as unique unless generating incredibly large numbers of them. Also, a file containing that many UUIDs would be 45 exabytes – stevec Jan 14 '21 at 05:34
  • Also, if you generated 103 trillion version-4 UUIDs, the probability of a duplicate is still just 1/1,000,000,000 – stevec Jan 14 '21 at 05:36
  • Here's how to include a timestamp to improve uniqueness: `token = "#{SecureRandom.uuid}.#{Time.now.to_i}" # => "96b0a57c-d9ae-453f-b56f-3b154eb10cda.1625419943"` – Ira Herman Jul 04 '21 at 17:58
41

How to create small, unique tokens in Ruby

>> require 'digest'
=> []
>> Digest::SHA1.hexdigest("some-random-string")[8..16]
=> "2ebe5597f"

>> SecureRandom.base64(8).gsub("/","_").gsub(/=+$/,"")
=> "AEWQyovNFo0" 

>> rand(36**8).to_s(36)
=> "uur0cj2h"
Cœur
  • 37,241
  • 25
  • 195
  • 267
Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
23

Did you look at UUIDTools?

UUIDTools was designed to be a simple library for generating any of the various types of UUIDs (or GUIDs if you prefer to call them that). It conforms to RFC 4122 whenever possible.

Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
16

Google yields the following Ruby library.

Also, over at ruby-forum they say you can install a gem (execute gem uuid on the command line to install it) and then do

gem 'uuid'
puts UUID.new

in your code to see a new UUID.

(Hint: I Googled for guid ruby)

Cadoiz
  • 1,446
  • 21
  • 31
Marc W
  • 19,083
  • 4
  • 59
  • 71
6

Small update to Simone Carletti answer:

SecureRandom.base64(8).gsub("/","_").gsub(/=+$/,"")

=> "AEWQyovNFo0" 

can be replaced with:

SecureRandom.urlsafe_base64(8)
Cadoiz
  • 1,446
  • 21
  • 31
5

To create a proper, mysql, varchar 32 GUID

SecureRandom.uuid.gsub('-','').upcase
Aaron Henderson
  • 1,840
  • 21
  • 20
1

While programming late at night I came up with the following solution (based off Simone's answer) for generating a unique GUID in Rails. I am not proud of it but it does work quite well.

while Order.find_by_guid(guid = rand(36**8).to_s(36).upcase).present?; end
Cadoiz
  • 1,446
  • 21
  • 31
robotmay
  • 1,318
  • 14
  • 22
1

This is a neet technique I learnt from JavaScript:

def uuid
    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".gsub("x") do
        "0123456789ABCDEF"[rand(16)]
    end
end

Although in a more 'ruby way' one could also do:

def uuid
    "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".gsub("x") do
        rand(16).to_s(16)
    end
end
Sancarn
  • 2,575
  • 20
  • 45
  • Could you provide a link to the javascript source? I used [the first method here](https://stackoverflow.com/a/21963136/4575793), broofa's old answer. – Cadoiz Jan 26 '23 at 10:54
  • 1
    pretty sure the source was broofa's too. It should be noted that the above does not comply with GUID v4 standards, it is just a UUID. But it should have the same performance and benefits of a GUID. Slightly more complex code is required to get a v4 compatible GUID. – Sancarn Jan 31 '23 at 01:47
1

For rails running a postgreSQL database do 2 things.

Step 1: generate a scaffold/model

rails g scaffold manager name:string --primary-key-type=uuid

Step 2: Add a line to migration file

If you rails db:migrate now, you'll get a PG::UndefinedFunction: ERROR: function gen_random_uuid() does not exist error.

So add this to the migration file

enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')

Example:

class CreateManagers < ActiveRecord::Migration[7.0]

  enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto')

  def change
    create_table :managers, id: :uuid do |t|
      t.string :name
      t.timestamps
    end
  end
end

You're done!

Oh one last thing, you only nee to add enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') to one migration file (e.g. the first time you use uuid type).

stevec
  • 41,291
  • 27
  • 223
  • 311
1

FOR NEW RAILS VERSIONS >= 5.1

For new Rails versions, you can now use the methods under Digest::UUID, e.g. Digest::UUID.uuid_v4

See API Docs:

Tilo
  • 33,354
  • 5
  • 79
  • 106
0

When I used uuid gems recommended in this question, no one can generate unique and random UUID. My answer is a work around, if we have gem later to satisfy the request, you'd better to use gem in Ruby.

I try most recommended uuid gems in this question, but no one make me satisfied, we need unique and random uuid. I directly run system command uuidgen in ruby, and I like the result, and share here.

puts `uuidgen`
8adea17d-b918-43e0-b82f-f81b3029f688
puts `uuidgen`
6a4adcce-8f64-41eb-bd7e-e65ee6d11231
puts `uuidgen`
51d5348b-8fc3-4c44-a6f7-9a8588d7f08a
puts `uuidgen`
332a0fa3-7b07-41e1-9fc8-ef804a377e4e

if compare with uuid gem, you will know the difference.

irb(main):003:0> uuid.generate
=> "40cdf890-ebf5-0132-2250-20c9d088be77"
irb(main):004:0> uuid.generate
=> "4161ac40-ebf5-0132-2250-20c9d088be77"

Test environment is linux and Mac OS environment.

BMW
  • 42,880
  • 12
  • 99
  • 116
  • 3
    a `puts \`...\`` is basically doing a system call to `uuidgen(3)` which fails on any other platform other than Linux, adds extreme amounts of execution time, and in general is really counter intuitive coding practice. Why would you choice such a method? – Dwight Spencer Sep 16 '15 at 21:15
  • 1
    @DwightSpencer I think we are in different area with different purpose. What you care is not in my concerns at all, such as the execute time, the wide range of operation systems , code migrations . I care the code can work in Mac OS or main stream Linux and get the right result I need. Of couse, if you can work out a way in Ruby and get the same result as uuidgen command, I am happy to use it. But until now, I didn't find any. – BMW Sep 16 '15 at 23:17
  • 1
    Both @J_ and @simone-carletti have already pointed out a better way on this post. I for one would suggest `SecureRandom` as that is preforming the same function in the same method as `uuidgen` but unlike `uuidgen`'s use of the blocking /dev/random only `SecureRandom` uses openssl's library first then drops to dev/urandom then finally /dev/random in attempts to do non blocking randomization generation. – Dwight Spencer Sep 17 '15 at 15:57