1
votes

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:

  1. undefined method 'email' for empty password field until the first user is created
  2. cannot make the error message for the exclusive 'name' parameter of the employee model show up in the same place as the error messages for the user model

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.

Registration view

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?

1

1 Answers

1
votes
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

  1. 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
    
  2. 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