Reputation: 6324
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
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
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
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