Last Updated: February 25, 2016
·
799
· trevorhinesley

Watch out for this validates_presence_of gotcha in Rails

I have a model that validates_presence_of :organization. Organization is another model. In order to prevent from having to make sure each controller that creates a User also establishes an Organization for this user (if one isn't passed in), I set up a before_validation method that assigns an Organization to the User. My original code for before_validation :assign_organization was the following:

validates_presence_of :organization

before_validation :assign_organization

def assign_organization
  if self.new_record? && self.organization.nil?
    organization = Organization.new
    organization.save
    self.organization = organization
  end
end

This seemed to work fine, until I realized that this was allowing a user to be created whether or not the organization actually saved. This was due to the fact that the organization object existed, so self.organization just pointed to that instance, whether or not organization saved succesfully. To solve this, I did the following:

validates_presence_of :organization

before_validation :assign_organization

def assign_organization
  if self.new_record? && self.organization.nil?
    organization = Organization.new
    organization.save
    self.organization_id = organization.id
  end
end

By simply changing self.organization = organization to self.organization_id = organization.id, the validates_presence_of :organization would return false successfully, because if organization didn't save, organization.id would be nil. Before, even if the organization object didn't save, the user's organization column temporarily pointed to the empty, invalid object, rather than something nil, which caused the validation to pass even if self.organization would be nil upon saving the User.

Hope this helps someone else!

T

P.S. Someone mentioned just trying self.organization = Organization.create, this won't work either. If you look at the docs, create returns an object whether it passes validations or not. This would result in the same scenario as my original code block.

EDIT:

As @boriscy mentioned in the comments below, you can actually do this as well:

validates_presence_of :organization

before_validation :assign_organization

def assign_organization
  if self.new_record? && self.organization.nil?
    organization = Organization.new
    organization.save! #throws an error if save fails
    self.organization = organization
  end
end

However, it's important to note that the original fix that I mentioned would automatically throw an error that is saved inside the user object's errors, but with the above alternative, you would need to handle the ActiveRecord error thrown by save! or the app will break in place. Thanks @boriscy!

2 Responses
Add your response

def assign_organization
  if self.new_record? && self.organization.nil?
    organization = Organization.new
    organization.save! # Raises exception on error
    self.organization = organization
  end
  rescue
   nil
end

better do this if you have has_one :organisation

def assign_organization
  if new_record? && organization.blank?
    self.build_organisation
  end
end
over 1 year ago ·

@boriscy The first option you gave is a good alternative. This will throw an error that would either need to be added to the user object, or caught somewhere else. Thanks for that!

However, build_organization doesn't work. It does the same thing as the original code. It sees an object (though organization doesn't save), and passes validation.

over 1 year ago ·