how to validate a record in table before saving in ruby on rails

Surjan Singh picture Surjan Singh · Apr 12, 2012 · Viewed 17.9k times · Source

I am new to Ruby on Rails I have a scenario in which I have a form which has some fields. One of the field values I need to validate against a table which has the data . I want to restrict the user from saving any data unless the field is validated with the table records.

Initially I added the code in controller to validate that but I have other fields which I need to validate as empty so it did not work .

Also I want the the validation error to be part of other errors.

I tried the below code in the model file

before_create :validate_company_id

def validate_company_id
    cp = Company.find_by_company_id(self.company)
    if @cp != nil
       return
     else
        self.status ||= "Invalid"    
     end
end

But its not validating , could you help me how I can validate it .

regards Surjan

Answer

shime picture shime · Apr 12, 2012

The guys answered correctly, but provided the other way for solution. You could ask yourself: "Why doesn't my code get executed?"

First of all, you have errors in your code - @cp is undefined. Also, I don't know what are you trying to achieve with self.status ||= "Invalid".

You also don't have to use self when you're calling an attribute, but you do have to call it when you're assignig a new attribute value. So self.company is unnecessary, you can just use company.

I've also noticed you have the company_id attribute in your companies table. That's not neccessary, common convention is using just an id instead. If you don't want to alter your table you can set the id field on your model like so:

class Company < ActiveRecord::Base
  set_primary_key :company_id

  # ... the rest of your model code ...
end

After that you can use Company.find instead of Company.find_by_company_id.

Okay, let's say you have the following code after the fixes:

before_create :validate_company_id

def validate_company_id
    cp = Company.find(company)
    if cp != nil
       return
     else
        self.status ||= "Invalid"    
     end
end

First of all I would like to use ternary operator here

before_create :validate_company_id

def validate_company_id
    Company.find(company) ? return : self.status ||= "Invalid"
end

Isn't this cleaner? It does the exact same thing.

Now about that self.status of yours. If you would like to invalidate the object in ActiveModel you have to set some values in errors hash. You're in misconception if you think that a model with the status attribute of "Invalid" is invalid. It's still perfectly valid model in Rails.

So how do you invalidate?

You put some values into errors hash. You can also specify a message and the attribute you're validation error refers to.

So let's do it on your model

before_create :validate_company_id

def validate_company_id
    Company.find(company) ? return : errors.add(:company,"Invalid Company ID")
end

Now if you try to save your model with invalid company_id it will still pass and get saved to the DB. Why is that?

It's because of the ActiveModels lifecycle. Your method gets called too late.

Here are all the callback methods you can use

Creating

before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save

Updating

before_validation
after_validation
before_save
around_save
before_update
around_update
after_update
after_save

Destroying

before_destroy
around_destroy
after_destroy

Notice how your method gets called long after the validation cycle. So you should not use before_create, but after_validation or before_validation callbacks instead.

And here we are with the working validation method of your model.

after_validation :validate_company_id

def validate_company_id
    Company.find(company) ? return : errors.add(:company,"Invalid Company ID")
end