3

One of the models in a Rails 3.1 application I'm working on has a "code" attribute that is generated automatically when the record is created and that must be unique. The application should check the database to see if the generated code exists and, if it does, it should generate a new code and repeat the process.

I can ensure the field's uniqueness at the database level with add_index :credits, :code, :unique => true (which I am doing) and also in the model with validates_uniqueness_of, but both of these will simply return an error if the generated code exists. I need to just try again in the case of a duplicate. The generated codes are sufficiently long that duplicates are unlikely but I need to be 100% certain.

This code generation is handled transparently to the end user and so they should never see an error. Once the code is generated, what's the best way to check if it exists and to repeat the process until a unique value is found?

2 Answers 2

4

Here's a quick example, there is still technically a race condition here, though unless your seeing hundreds or thousands of creates per second it really shouldnt be a worry, worst case is your user gets a uniquness error if two creates are run in such a way that they both execute the find and return nil with the same Url

class Credit < ActiveRecord::Base
  before_validation :create_code, :if => 'self.new_record?'
  validates :code, :uniqueness => true

  def create_code
    self.code = code_generator
    self.code = code_generator until Credit.find_by_code(code).nil?
  end
end

If you absolutely needed to remove the race condition case where two creates are running in tandem and both trigger the find with the same code and return nil you could wrap the find with a table lock which requires DB specific SQL, or you could create a table that has a row used for locking on via pessimistic locking, but I wouldn't go that far unless your expecting hundreds of creates per second and you absolutely require that the user never ever sees an error, it's doable, just kind of overkill in most cases.

Sign up to request clarification or add additional context in comments.

1 Comment

Ah yes, until. This should do nicely, thanks. The creation rate is closer to one every week or two so I don't expect any collisions here.
0

I am not sure if there is a built in way. I have always used a before_create.

Here is an example in the context of a UrlShortener.

class UrlShortener < Activerecord::Base
  before_create :create_short_url

  def create_short_url
    self.short_url = RandomString.generate(6)
    until UrlShortener.find_by_short_url(self.short_url).nil?
      self.short_url = RandomString.generate(6)
    end
  end
end

2 Comments

I'd put it in a before_validation myself.
before_create wont work with a uniqueness validation constraint as the before_create callback would never get triggered. The before_validation option works, just remember to add an :if => 'self.new_record?' clause so it does not trigger on update

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.