Reputation:
I have two models in my app, one 'user' model with devise, one 'employee' model made by scaffolding. I need a way to have the employee table populated as soon as a new user registers, both tables share some params, some are exclusive. The employee belongs to the user model, each user has one employee. The view I use is the devise user registration form with nested attributes to allow for the employee params. Creation is handled by the user controller. Problems that occur:
employee-model:
class Employee < ApplicationRecord
audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
user model
class User < ApplicationRecord
audited
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable
has_one :employee, inverse_of: :user
accepts_nested_attributes_for :employee
validates_presence_of :password
validates :password, format: { with: /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§@?!`´~#'._,;<>|°()"{}="@$%^&*+-]]).{8,}/}, if: -> {password.present?}
validates_presence_of :password_confirmation, if: -> {password.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§@?!`´~#'._,;<>|°()"{}="@$%^&*+-]]).{8,}/}
validates_confirmation_of :password, if: -> {password.present? and password_confirmation.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§@?!`´~#'._,;<>|°()"{}="@$%^&*+-]]).{8,}/}
validates_presence_of :email
validates :email, format: { with: /\A([^@[0-9]\s]+\.)+([^@[0-9]\s]+)@((thisapp+\.)+com)\z/i}, uniqueness: true, if: -> {email.present?}
end
user controller
class Users::RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
super
end
def new_employee
@employee = Employee.new
end
# POST /resource
def create
@employee = Employee.new(employee_params)
super
@employee.email = User.last.email
@employee.user_id = User.last.id
@employee.created_by_id = User.last.id
@employee.save
end
user registration view
.card style='text-align:center;'
.card-body
h2.card-title style='text-align:center;' = t('.sign_up')
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= render "users/shared/error_messages", resource: resource
br
.field
= f.label :email, t('users.email_field')
br
= f.text_field :email, autofocus: true, autocomplete: "email"
.field
= f.label :password, t('users.password_field')
/- if @minimum_password_length
em
= t('devise.shared.minimum_password_length', count: @minimum_password_length)
br
= f.password_field :password, autocomplete: "new-password"
.field
= f.label :password_confirmation, t('users.password_confirmation_field')
br
= f.password_field :password_confirmation, autocomplete: "new-password"
= fields_for :employee do |e|
= e.label :name
= e.text_field :name
So, when I use @employee.email = User.last.email
in the controller I get a 'unknown method 'email' error when not filling out the password field, unless I have a preexisting user, then I get my custom error messages for not filling out the email. I guess it is because I am looking for a last user who does not exist at this point. Could seed a user, but that seems hackish. Tried using @employee.email = User.last(params[:email])
which leads to the email being saved as some hash value, but at least I get my error messages. Is there a way to convert that hash to the real email address again?
The other issue is the validation of the name field. Validation is asked for in the employee model, and user model accepts nested attributes, but that does not seem to be enough.
I did
if params[:employee][:name].blank?
flash[:notice] = t('.noname')
which works insofar as that the form cannot be submitted without some value in the name field, but messes up my error messages. Shows a flash message where all other errors (no email/password/pw confirmation) are handled by the devise's shared error messages as non-flash messages:
- resource.errors.full_messages.each do |message|
li
= message
So having the blank name as a flash message would look inconsistent, and the spot for the flash message is already reserved.
Flash message is on top, 'following errors prevent..', actual error messages are below 'Registrieren', and that is also where the error message for blank name would need to be.
Any ideas on how to approach this or maybe a better solution than handling this stuff in the user controller?
Upvotes: 1
Views: 100
Reputation: 1521
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
super
end
def new_employee
@employee = Employee.new
end
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, employee_attributes: %i[name])
end
This signup param will be used while creating a user from devise registration controller. Since we used nested attributes, passing the arguments along with the parent object will handle employee creation(https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)
For assigning email for employee
The one way is to merge the email from the user params.
def sign_up_params
sign_up_param = params.require(:user).permit(:email, :password, :password_confirmation,employee_attributes: %i[name])
sign_up_param[:employee_attributes].merge!(email: params[:user][:email])
sign_up_param
end
Or maybe you could assign the email of the employee from the user before the validation.
class Employee < ApplicationRecord audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
# Callbacks
before_validation :set_email
# Methods
# Set email of the employee from the user.
def set_email
self.email = self.user.email
end
Upvotes: 0