Cuidado com este validates_presence_of gotcha no Rails

Eu tenho um modelo que validates_presence_of :organization. Organização é outro modelo. Para evitar que cada controlador que cria um Usuário também estabeleça uma Organização para esse usuário (se não for passada), configurei um before_validationmétodo que atribui uma Organização ao Usuário. Meu código original para before_validation :assign_organizationera o seguinte:

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

Isso parecia funcionar bem, até que percebi que estava permitindo que um usuário fosse criado, independentemente de a organização ser salva ou não. Isso se deveu ao fato de que o organizationobjeto existia, então apenas apontava para aquela instância, salvo ou não com sucesso. Para resolver isso, fiz o seguinte:self.organizationorganization

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

Simplesmente mudando para , o retornaria false com sucesso, porque se não salvasse, seria . Antes, mesmo que o objeto não fosse salvo, a coluna do usuário apontava temporariamente para o objeto vazio e inválido, ao invés de algo , o que fazia passar a validação mesmo que fosse ao salvar o usuário.self.organization = organizationself.organization_id = organization.idvalidates_presence_of :organizationorganizationorganization.idnilorganizationorganizationnilself.organizationnil

Espero que isso ajude mais alguém!

T

PS Alguém mencionou apenas tentar , isso também não funcionará. Se você olhar para os documentos, retorna um objeto se ele passa nas validações ou não. Isso resultaria no mesmo cenário do meu bloco de código original.self.organization = Organization.createcreate

EDITAR:

Como @boriscy mencionou nos comentários abaixo, você também pode fazer isso:

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

No entanto, é importante observar que a correção original que mencionei geraria automaticamente um erro que é salvo dentro dos usererros do objeto, mas com a alternativa acima, você precisaria lidar com o erro do ActiveRecord lançado ou o aplicativo deixará de funcionar. Obrigado @boriscy !save!