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.