Reputation: 11384
I want to replace an object's url from /object/123
to /object/f8a3b2
, so instead of using the id
column I'm generating an uid
column manually when the object is created.
I can use the uuid libraries available to generate uid, but how do I make sure that the generated value is always unique?
I have a unique index set on the uid column, so if it's not unique, it should throw back an error - can I catch that on the model level, create another uid, and try again?
Upvotes: 1
Views: 3767
Reputation: 33954
If you're using the uuid gem you don't have to worry about uniqueness, as this has been taken care of for you. However, uuid will generate long, 128-bit unique IDs. So, if you want something that's shorter, you might not want to use it.
If you want, say, 8-digit unique codes which are also random, you can see an implementation of such a system in my cortex project under the new_unique_code
method.
What this does is generate an 8-digit alpha-numeric code to represent a resource in the app. After generating a given code I try to do a .find_by_code
, and if that returns nil
I know that it's NOT already in use.
In my particular case, 8-digit alpha numeric codes give me a keyspace of some many millions of possible codes (I forget how many). Based on the use case of my app, the chances of a code collision (the percentage of used codes vs. available codes) are small enough to risk possibly needing to generate a new code more than once for a given resource.
The result of this is that needing to generate a new code more than once for a given resource is extremely rare, and as such, takes up almost no additional time.
One limitation of my solution is that if you have multiple app servers, but a single DB server, it's possible to have two independent app servers generate the same code before either of them creates the new resource record in the centralized DB. This creates a situation where it's possible to have code collisions and all but the first record saved to the DB will be marked invalid (due to a uniqueness constraint on the code column). This particular app only runs (and will probably only ever run) on a single app server instance. This is a known tradeoff that I'm aware of, so I can risk it.
But, if I wanted to solve this problem, I would limit each app server's range of random codes such that they could never accidentally overlap. For example, let's say I'm generating random codes that come out as 8-digit numeric codes between the values 00000000-99999999. If I had, say, three app servers running this app, I would simply limit server #1 to generating codes between 00000000-33333333, server #2 to 33333334-66666666, and server #3 to 66666667-99999999. This way they would be guaranteed never to overlap codes, and you could still generate unique, random keys all day long.
How exactly you would limit a given server to a given range is a bit off topic, but some of the available options are:
Upvotes: 1
Reputation: 11081
There's probably better ways to do this, but the first thing that popped in to my head was making use of the validates_uniqueness_of
method on your active record model, as you were suggesting.
In your model:
class MyUniqueModel < ActiveRecord::Base
validates_uniqueness_of :uuid_column
end
In your controller
@uniqueObject = MyUniqueModel.new(modelParams)
@uniqueObject.uuid_column = generate_uuid() # whatever method you use for generating your uuid
while !uniqueObject.save # if at first it didn't save...
uniqueObject.uuid_column = generate_uuid() # try, try again
end # should break out of the loop if the save succeeded.
If you have other validation on your model, then the problem you'll encounter with my suggestion is determining whether the save failure was caused by your apparently non-unique uuid, or some other validation error. This means you'll have to filter the loop body by the failed column to figure out whether you should bother re-generating another UUID.
Upvotes: 4
Reputation: 15808
You should use
validates :uid, :uniqueness => true
I must add a warning to this advice since there is still a scenario where you will get an error from your DB for duplicate uid column value. You can find more about it in Michael Hartl's RoR tutorial
In order to catch and retry saving upon duplicate uid you could do something like:
model = Model.create(:uid => generate_uid)
if model.errors[:uid].any?
model.create(:uid => generate_uid)
end
Upvotes: 0
Reputation: 10856
Using Rails Validations to validate the uniqueness:
class MyModel < ActiveRecord::Base
validates :id, uniqueness: true
end
Upvotes: 0