The simplest MRE is rails g scaffold users
As we create users, their id will be 1, 2, 3, 4 etc, and their 'show' route will be /show/:id
Sometimes we don't want users of the website to know exactly how many users we have - is there an easy way to obfuscate this?
I can think of a way to do it manually, by adding an additional column on sign up with a random 12 digit integer but also checking that the randomly generated integer doesn't already exist.
But I wonder is there a smarter way?
An example: https://www.youtube.com/watch?v=Gzj723LkRJY (where Gzj723LkRJY
is presumably randomly generated and not something predictable)
Example of why incremental ids can be a bad idea (~ 30 seconds from 2m 52s)
lots of badly designed sites use incremental counters. It might tell your competitors how many customers you have .. It might let people download all your records easily
..and another example of why incremental ids/routes can be bad:
each post carried a numerical ID that was incremented from the ID of the most recently published one
Rails does not actually care about what you use a primary identifier for your models.
As we create users, their id will be 1, 2, 3, 4 etc, and their 'show' route will be /show/:id
Incorrect. The routes will be /users/?(:id)
. Run rails routes | grep users
to see the routes generated.
Your routes actually will match not just integers but any characters except a limited subset (.
and /
in particular) to the named :id
segment.
Thus is will match the following to the users#show action:
GET /users/1
GET /users/gabba-gabba-hey
GET /users/gabba%20gabba%20hey
But not:
GET /users/1/2
GET /users/gabba/gabba
GET /users/gabba/gabba/hey
Auto-incrementing integer columns are used by default as the primary key in Rails as they are simple, short and do the job 99% of the time. Usually if you switch to UUIDs the reason is that you're operating at a scale where you need multiple databases and generating ids sequentially does not cut it. Switching the identifier to something randomly generated makes it infinitely harder to guess but is still not a replacement for actually authorizing access to resources as they can still be leaked to logs, emails etc.
I can think of a way to do it manually, by adding an additional column on sign up with a random 12 digit integer but also checking that the randomly generated integer doesn't already exist. But I wonder is there a smarter way?
Yes. Don't reinvent the wheel.
Ruby already has a built in SecureRandom#uuid method which generates UUIDs according to RFC 4122:
class User < ApplicationRecord
def generate_uuid
loop do
self.uuid = SecureRandom.uuid
break unless User.exists?(uuid: self.uuid)
end
end
end
As the risk of collision here is extremely low this loop might not ever run more than once, and its simply running a EXIST
query on a (hopefully) indexed primary key - so the cost is relatively low.
By using not just integers but also letters you can generate shorter identifiers with less chance of collision.
If you are using a database that has built in UUID support like Postgres this is a better alternative as this is job that really is best solved on the database level as it avoids bloating the application and reduces overhead.