1

I have a table and have the validation for uniqueness setup in the table. eg.

create table posts ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY UNIQUE, title varchar(255) unique, content text );

Here title is unique. Do also need to inform the model class about this uniqueness? If not when i insert a duplicate title, it gives me error. How do I catch that. Currently rails shows me the backtrace and i could not put my own error messages

def create
  @f = Post.new(params[:post])
  if @f.save
    redirect_to posts_path
  else
    @flash['message'] = "Duplicated title"
    render :action=>'new'
  end 
end 

I am not being redirected to the new and instead show a big backtrace.

Mischa
  • 42,876
  • 8
  • 99
  • 111
kiran
  • 477
  • 1
  • 7
  • 11
  • as a note change @flash to flash and provide more in depth errors on the form itself, saying that the title was duplicated is no good since the model may have changed - try an inline error in the form itself. – Omar Qureshi Sep 23 '11 at 18:23

4 Answers4

2

Use the validates_uniqueness_of validation. "When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified attribute (that maps to a column)"

Ransom Briggs
  • 3,025
  • 3
  • 32
  • 46
0

You will have to add all of the validations to your models. There is a gem called schema_validations, which will inspect your db for validations and create them in your models for you. https://github.com/lomba/schema_validations

Sean Hill
  • 14,978
  • 2
  • 50
  • 56
0

Yes you do as noted in other answers, the answer is validate_uniqueness_of - http://ar.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html#M000086. Note, even though you have a validation in your model a race condition does exist where Rails may try and do two inserts unaware of there being a unique record already in the table

When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.

Because this check is performed outside the database there is still a chance that duplicate values will be inserted in two parallel transactions. To guarantee against this you should create a unique index on the field. See add_index for more information.

So what you have done, by creating a unique index on the database is right, though you may get database driver exceptions in your exception log. There are workarounds for this, such as detecting when inserts happen (through a double click).

Omar Qureshi
  • 8,963
  • 3
  • 33
  • 35
  • my problem is here. If a database error occurs, even after i had validations in the model, i need to catch and show them instead of dumping the stack trace to the user. Is exception catching the only way here by using begin and rescue – kiran Sep 23 '11 at 17:24
  • validates_uniqueness_of is sufficient for 99% of cases. However it can happen that two clicks happen in a short period of time, so User clicks to insert once, then clicks again very fast. The rails app does a select * from table where title = 'whatever' and doesnt find anything for the first click, it then does it again for the second click. Both clicks then go through the process of inserting the record, the first click succeeds whereas the second click fails to insert due to the unique constraint. – Omar Qureshi Sep 23 '11 at 18:22
  • Last I knew the unique key database exception was lumped into the catch all database exception, so in order to trap it properly, you would have to rescue, then look at the message to see if it was a constraint violation (which is database specific and gross) and render errors if it was, and rethrow if it were a different exception. – Ransom Briggs Sep 23 '11 at 21:39
  • For this sort of thing, I would say just do validates_uniqueness_of and deploy. If after a month in production, you find that it is actually a problem, then do the extra work. – Ransom Briggs Sep 23 '11 at 21:40
  • Ransom Briggs is totally correct, just pointing out that the vuo is not the complete solution. – Omar Qureshi Sep 23 '11 at 23:31
0

The Michael Hartl Rails Tutorial covers uniqueness validation (re. the "email" field) here. It appears the full uniqueness solution is:

  1. Add the :uniqueness validation to the model.
  2. Use a migration to add the unique index to the DB.
  3. Trap the DB error in the controller. Michael's example is the Insoshi people_controller--search for the rescue ActiveRecord::StatementInvalid statement.

Re. #3, it looks like Michael just redirects to the home page on any DB statement exception, so it's not as complex (nor as accurate) as the parsing suggested by @Ransom Briggs, but maybe it's good enough if, as @Omar Qureshi says, the uniqueness constraint covers 99% of the cases.

Mark Berry
  • 17,843
  • 4
  • 58
  • 88