lesce
lesce

Reputation: 6324

Rails unique column has duplicates

This is the stripped down version of my model .

model Paper
  PAPER_STARTING_NUMBER = 1

  validate_uniqueness_of :number, :allow_blank => true
  before_create :alocate_paper_number

  def alocate_paper_number
    return true if self.number.present?
    p_number = Paper.maximum('number') || Paper::PAPER_STARTING_NUMBER 
    self.number = p_number >= Paper::PAPER_STARTING_NUMBER ? p_number+1 : Paper::PAPER_STARTING_NUMBER 
    return true 
  end
end

the problem is I have duplicates in the number column . Any ideas why and how I can fix this without changing the callback . I know I could add a uniqueness validation on the database or make a sequence on that column , any other ideas ?

Upvotes: 1

Views: 239

Answers (3)

lesce
lesce

Reputation: 6324

How I fixed it ( bare in mind that I couldn't return a validation error ) I've added a uniquness index on the number column ( as mu and Hugo suggested ) and because I couldn't return a validation error in the controller

class PaperController < ApplicationController
  def create
    begin
      @paper.save
    rescue ActiveRecord::RecordNotUnique
      @paper.number = nil
      create
    end
  end
end

Upvotes: 1

Hugo Logmans
Hugo Logmans

Reputation: 2252

It is in de docs. validate_uniqueness_of TRIES to make it unique. But if two processes add one record at the same time, they both can contain the same number.

If you want to guarantee uniqueness, let the database do it. But because that is different for each DB, Rails does not support it by design.

It's explained here: http://guides.rubyonrails.org/active_record_validations_callbacks.html#uniqueness

With the solution: "To avoid that, you must create a unique index in your database."

Upvotes: 2

Trip
Trip

Reputation: 27114

First you have to understand the order of callbacks :

(-) save

(-) valid

(1) before_validation

(-) validate

(2) after_validation

(3) before_save

(4) before_create

(-) create

(5) after_create

(6) after_save

(7) after_commit

So as you can see , it validates the uniquity of your number attribute, and then before_create can at its own disposal go against what your validation wants to accomplish.

In regards to a more cleaner architecture, I would put both of these ideas together in your custom model, as it doesn't seem that the number can be choosen by the User. It's just an incrementer, right?

def alocate_paper_number
  p_number = Paper.maximum('number') || Paper::PAPER_STARTING_NUMBER 
  self.number = p_number + 1
end

That snippet alone, would prevent duplicates, as it always increments upwards ( unless, there's the possibility of the number going the other way that I'm not aware of ), and also there's no reason to return all those trues. Its true enough!

Upvotes: 2

Related Questions