CONTEXT
I'm using devise_invitable to allow a user (with an admin role) to register another user in my app.
User objects have an email, password, token (random string), role (also string) and an associated HealthRecord object which has name, last name, dni (personal id) plus some extra info
PROBLEM
For some reason, when I input an existing email, I get an error (which is intended validation) but it also destroys the HealthRecord associated with the user who has that existing email.
CODE
This is what my console shows upon trying to create the user with existing email
Started POST "/users/invitation" for ::1 at 2021-11-26 10:04:15 -0300
Processing by Users::InvitationsController#create as HTML
Parameters: {"authenticity_token"=>"[FILTERED]", "user"=>{"email"=>"paciente1@example.com", "role"=>"Paciente", "health_record_attributes"=>{"residencia"=>"Cementerio", "nombre"=>"overriding", "apellido"=>"test", "dni"=>"123456789", "risk"=>"0", "birth"=>"1999-02-12"}}, "commit"=>"Registrar"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT ? [["id", 5], ["LIMIT", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."email" = ? ORDER BY "users"."id" ASC LIMIT ? [["email", "paciente1@example.com"], ["LIMIT", 1]]
HealthRecord Load (0.1ms) SELECT "health_records".* FROM "health_records" WHERE "health_records"."user_id" = ? LIMIT ? [["user_id", 1], ["LIMIT", 1]]
TRANSACTION (0.1ms) begin transaction
HealthRecord Destroy (0.5ms) DELETE FROM "health_records" WHERE "health_records"."id" = ? [["id", 1]]
TRANSACTION (207.2ms) commit transaction
User Exists? (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? AND "users"."id" != ? LIMIT ? [["email", "paciente1@example.com"], ["id", 1], ["LIMIT", 1]]
HealthRecord Exists? (0.4ms) SELECT 1 AS one FROM "health_records" WHERE "health_records"."dni" = ? LIMIT ? [["dni", "123456789"], ["LIMIT", 1]]
Rendering layout layouts/application.html.erb
Rendering users/invitations/new.html.erb within layouts/application
HealthRecord Load (0.1ms) SELECT "health_records".* FROM "health_records" WHERE "health_records"."user_id" = ? LIMIT ? [["user_id", 5], ["LIMIT", 1]]
↳ app/views/users/invitations/new.html.erb:18
The view to generate the new user
<h2>Registro excepcional</h2>
<%= form_for(setup_user(resource), as: resource_name, url: invitation_path(resource_name), html: { method: :post }) do |f| %>
<% resource.class.invite_key_fields.each do |field| -%>
<div class="field">
<%= f.label field %><br />
<%= f.text_field field, class: 'form-control'%>
</div>
<% end %>
<div class="field">
<%= f.hidden_field :role, :value=>"Paciente"%>
</div>
<%= f.fields_for :health_record do |ff| %>
<div class="field">
<%= ff.hidden_field :residencia, :value=>current_user.health_record.residencia%>
</div>
<div class="field">
<%= ff.label "Nombre" %><br/>
<%= ff.text_field :nombre, class: 'form-control',:required => true%>
</div>
<div class="field">
<%= ff.label "Apellido" %><br/>
<%= ff.text_field :apellido, class: 'form-control',:required => true%>
</div>
<div class="field">
<%= ff.label "DNI" %><br/>
<%= ff.text_field :dni, class: 'form-control',:required => true%>
</div>
<div class="field">
<%= ff.label "Es de riesgo:",:required => true %>
<%= ff.check_box :risk %>
</div>
<div class="field">
<%= ff.label "Fecha de nacimiento:"%><br/>
<%= ff.date_field :birth, class: 'form-control',:required => true%>
</div>
<% end %>
<br/>
<div class="actions">
<%= f.submit "Registrar" %>
</div>
<% end %>
The user model which has validate_on_invite
class User < ApplicationRecord
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :authentication_keys => [:token,:email], :validate_on_invite => true
has_many :comprobantes, :dependent => :destroy
has_one :health_record, :dependent => :destroy
has_many :TurnoAsignado, :dependent => :destroy
has_many :TurnoNoAsignado, :dependent => :destroy
validates :email, uniqueness: true
before_save :init
accepts_nested_attributes_for :health_record
def init()
if self.token.nil?
self.token = (rand()*1000000).to_i
end
end
end
The HealthRecord model
class HealthRecord < ApplicationRecord
belongs_to :user
validates :dni, presence: true
validates :dni, uniqueness: true
validates :nombre, presence: true
validates :apellido, presence: true
validates :birth, presence: true
before_save :upcase_content
def upcase_content
self.nombre=self.nombre.downcase
self.apellido=self.apellido.downcase
self.nombre=self.nombre.split(/ |\_/).map(&:capitalize).join(" ")
self.apellido=self.apellido.split(/ |\_/).map(&:capitalize).join(" ")
end
end
The invitation controller (it's pretty much default I just added parameters and an after_path)
class Users::InvitationsController < Devise::InvitationsController
before_action :configure_permitted_parameters
#Permit the new params here.
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:invite, keys: [
:token,
:role,
health_record_attributes: [
:apellido,
:nombre,
:dni,
:risk,
:birth,
:residencia
]
])
end
def after_invite_path_for(resource)
new_asignado_path(self.resource.id)
end
end
I think the problem may have been this line
form_for(setup_user(resource),...
I am using a helper to set the HealthRecord of a user to an empty one (fields_for needed the user to have a HealthRecord to work)
module FormHelper
def setup_user(user)
user.health_record ||= HealthRecord.new # ||= means “assign this value unless it already has a value”
user
end
end
Maybe what was happening is that the empty HealthRecord was assigned to the existing user, somehow?
I solved it by intercepting the flow of the create method in the invitations controller, by asking if the user email or dni exists
def create
@correo = User.find_by(email:params[:user][:email])
@dni = HealthRecord.find_by(dni:params[:user][:health_record_attributes][:dni])
if (@correo.nil? && @dni.nil?) #si no existe mail ni dni
super
else
mensaje="Los siguientes campos ya estan registrados:"
if !(@correo.nil?)
mensaje = mensaje + " email"
end
if !(@dni.nil?)
mensaje = mensaje + " dni"
end
flash[:notice] = mensaje
redirect_to new_user_invitation_path
end
end
Although this works, I'm not sure about the reason of the problem, any insight is welcome