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!
Written by Trevor Hinesley
Related protips
2 Responses
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@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.