How to separate change password from devise form

Jeremy Lynch picture Jeremy Lynch · Jul 11, 2013 · Viewed 13k times · Source

I am trying to do two things:

1) Change the default "edit user form" - provided with devise - to remove "password" and allow the other fields to be updated without having to enter a password ie remove the default validation for password.

2) Create a separate form for changing password

I have got everything to work, there is only one problem, in the separate form for updating password, I have included a field for current password. When using the form, no validation is made for current password, so I changed

@user.update_attributes(params[:user]) 

to

@user.update_with_password(params[:user])

This worked, however it raised another issue. Back in the main form with all the other details except password, the form now asks for a "current password". How can I achieve this without a validation for current password being called on the main form?

here is my registrations controller:

def update
  @user = User.find(current_user.id)
  if @user.update_attributes(params[:user])
    set_flash_message :notice, :updated
    # Sign in the user bypassing validation in case his password changed
    sign_in @user, :bypass => true
    redirect_to after_update_path_for(@user)
  else
    clean_up_passwords(resource)
    respond_with_navigational(resource) do
      if params[:change_password] # or flash[:change_password]
        render :change_password
      else
        render :edit
      end
    end
  end
end

Thanks!

Solution 1

I have found a solution to the problem (albeit a very messy one):

def update
  @user = User.find(current_user.id)

  if params[:user][:password].blank?
    if @user.update_attributes(params[:user])
      set_flash_message :notice, :updated
      # Sign in the user bypassing validation in case his password changed
      sign_in @user, :bypass => true
      redirect_to after_update_path_for(@user)
    else
      respond_with_navigational(resource) do
        render :edit
      end
    end
  else
    if @user.update_with_password(params[:user])
      set_flash_message :notice, :updated
      # Sign in the user bypassing validation in case his password changed
      sign_in @user, :bypass => true
      redirect_to after_update_path_for(@user)
    else
      clean_up_passwords(resource)
      respond_with_navigational(resource) do
        render :change_password
      end
    end
  end

Solution 2

Can you suggest a better solution?

Answer

steakchaser picture steakchaser · Aug 8, 2015

The accepted answer does not fully address the question. Which, I believe is to have a separate form for user profile attributes (like email, first name, etc) vs. the password. Here's what you need to do for that:

First, leverage the Devise::RegistrationsController for your profile updates.

  • Customize the view and remove the password and password_confirmation fields. Devise ignores these if they are not present in the put.
  • If you don't want to require the current password to make profile changes, read this. Not recommended; not secure.

Second, create your own controller to manage the password updates and your own helper to require current_password, password, and password_confirmation on update.

class PasswordsController < ApplicationController
  before_filter :authenticate_user!

  def edit
    @user = current_user
  end

  def update
    @user = User.find(current_user.id)
    if @user.update_password_with_password(user_params)
      # Sign in the user by passing validation in case their password changed
      sign_in @user, :bypass => true
      redirect_to edit_password_path, flash: { success: "Successfully updated password" }
    else
      render "edit"
    end

  end

  private

    def user_params
      params.require(:user).permit(:current_password, :password, :password_confirmation)
    end

end

Here's the helper, update_password_with_password that will require the new password fields.

class User < ActiveRecord::Base
  def update_password_with_password(params, *options)
    current_password = params.delete(:current_password)

    result = if valid_password?(current_password)
               update_attributes(params, *options)
             else
               self.assign_attributes(params, *options)
               self.valid?
               self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
               false
             end

    clean_up_passwords
    result
  end
end