Devise update user without password

I want to update users attributes without password in devise. The case is like, if password and password confirmation fields are not blank then I need devise error and if they are blank then other user attributes need to be updated. How could I do this with devise?

Thanks in advance!

57207 次浏览

Is this what you're look for? From the Devise wiki

Allow users to edit their account without providing a password

It shows how to do it for rails 3 and rails 4

=======================

John's solution is a simpler alternative

I solved this problem as follows: if form submitting with password, then need fill current_password field or form updated without password and current_password

in model:

class User
devise: bla-bla-bla


attr_accessor :current_password
end

In controller:

class Users::RegistrationsController <  Devise::RegistrationsController
layout 'application'




def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)




# custom logic
if params[:user][:password].present?
result = resource.update_with_password(params[resource_name])
else
result = resource.update_without_password(params[resource_name])
end


# standart devise behaviour
if result
if is_navigational_format?
if resource.respond_to?(:pending_reconfirmation?) && resource.pending_reconfirmation?
flash_key = :update_needs_confirmation
end
set_flash_message :notice, flash_key || :updated
end
sign_in resource_name, resource, :bypass => true
respond_with resource, :location => after_update_path_for(resource)
else
clean_up_passwords resource
respond_with resource
end
end
end

I think this is a much better solution:

if params[:user][:password].blank? && params[:user][:password_confirmation].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end

This prevents you from having to change the Devise controller by simply removing the password field from the form response if it is blank.

Just be sure to use this before @user.attributes = params[:user] or whatever you use in your update action to set the new parameters from the form.

I think with Devise 3.2+ you can just override update_resource in your subclassed controller. Here's an example for the question originally asked:

class MyRegistrationsController < Devise::RegistrationsController
protected


def update_resource(resource, params)
resource.update_without_password(params)
end
end

I solve this problem with my interface. There are two ways it can go.

Disable the fields if they are empty

This bit of jQuery disables the fields before the form submits in the event that they are empty. It requires a certain markup pattern, check the demo on Codepen.

$(".password-fields").each(function() {
var $fields, $form;
$form = $(this).parents("form");
$fields = $(this).find("input");
$form.on("submit", function() {
$fields.each(function() {
if ($(this).val() === "") {
$(this).attr("disabled", "disabled");
}
});
});
});

Give the user a checkbox to select if they want to update

Another option is to remove the password fields from the form if the user hasn't indicated that they want to update their password. Here's the example on CodePen.

$(".password-fields").each(function () {
var $fields, $button, html;
$fields = $(this);
$button = $fields.prev(".update-password").find("input");
html = $fields.html();


$button
.on("change", function () {
if ( $(this).is(":checked") ) {
$fields.html(html);
}
else {
$fields.html("");
}
})
.prop("checked", "checked")
.click();
});

In either case, it requires no update to the app itself. You are just submitting the fields you want to change.

If you want Devise to check current password only when user tries to change password (that means you can change other attributes without providing current password):

class RegistrationsController < Devise::RegistrationsController


protected


def update_resource(resource, params)
if params[:password].blank? && params[:password_confirmation].blank?
resource.update_without_password(params)
else
super
end
end
end

Also in your model:

attr_accessor :current_password

And don't forget about

devise_for :users, controllers: {registrations: 'registrations'}

in routes.rb.

I instead override #password_required? method inside user model.

class User < ActiveRecord::Base
devise :database_authenticatable, :validatable #, ...


def password_required?
if respond_to?(:reset_password_token)
return true if reset_password_token.present?
end
return true if new_record?
password.present? || password_confirmation.present?
end
end

So if user is new he will be required to specify password. However exist user will be required to specify password only if he fills in either password or password_confirmation attributes.

For more details see: https://github.com/plataformatec/devise/blob/master/lib/devise/models/validatable.rb#L33

My implementation is almost identical as original: https://github.com/plataformatec/devise/blob/master/lib/devise/models/validatable.rb#L53

except that I check for presence (which returns false for blank strings)

Here's discussion about my pull request on this matter: https://github.com/plataformatec/devise/pull/3920

It's more business logic so I wouldn't put too much code in the controller. I suggest overriding Devise::Models::Validatable#password_required? in your model:

def password_required?
new_record? || password.present? || password_confirmation.present?
end

If you still want to support password change but make it optional, check the availability of current_password as such:

class MyRegistrationsController < Devise::RegistrationsController
protected


def update_resource(resource, params)
if params[:current_password].blank?
resource.update_without_password(params.except(:current_password))
else
resource.update_with_password(params)
end
end
end

That way if the current_password is present you can proceed and update the password, else ignore it and update without password.

I had the exact same issue and this the solution I came up with and believe me it works. What i did is create a second user_params method and named it user_params_no_pass anyway take a look: what is happening here is that the admin will provide a password when the password need to be updated else leave the password blank. when the password is blank the user_params_no_pass is used else user_params. I hope that helps

    def update


if @user.valid_password?(params[:user][:password])
respond_to do |format|


if @user.update(user_params)
format.html { redirect_to @user, notice: 'User profile was successfully updated.' }
format.json { render :show, status: :ok, location: @user }
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
else
respond_to do |format|


if @user.update(user_params_no_pass)
format.html { redirect_to @user, notice: 'User profile was successfully updated without password.' }
format.json { render :show, status: :ok, location: @user }
else
format.html { render :edit }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
end


def destroy
@user.destroy
respond_to do |format|
format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
format.json { head :no_content }
end
end
private


def set_user
@user = User.find(params[:id])
end


def user_params
params.require(:user).permit(:user_name, :first_name, :middle_name, :last_name, :dob, :gender, :race, :hispanic, :leader, :mentor, :student, :email, :organization_id, :password, :opus,
:release_date, :days_to_release, :current_level, :is_active)
end




def user_params_no_pass
params.require(:user).permit(:user_name, :first_name, :middle_name, :last_name, :dob, :gender, :race, :hispanic, :leader, :mentor, :student, :email, :organization_id, :opus,
:release_date, :days_to_release, :current_level, :is_active)
end

As workaround put in your User model

def password_required?
encrypted_password.blank? || encrypted_password_changed?
end

I find this slight variation on the code above easier to follow:

def update
@user = User.find(params[:id])


method = if user_params[:password].blank?
:update_without_password
else
:update_with_password


if @user.send(method, user_params)
redirect_to @user, notice: 'User settings were saved.'
else
render :edit
end
end

After much exploration of the above possibilities I finally found a solution which allows you to update some attributes without a password and some with:

I made a view for user/edit.html.erb with the following form in it.

<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>


<%= f.submit "Save changes"%>
<% end %>

I set routes in routes.rb

resources :users, only: [:show, :index, :edit, :update]

I made edit and update methods in users_controller.rb

def edit
@user = current_user
end


def update
@user = current_user
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to root_url
else
render 'edit'
end
end




def user_params
params.require(:user).permit(:name, :avatar, :whatsup)
end

I used this edit view for changes which did not require a password. It skips the devise registration controller entirely because I link to it with

edit_user_path(current_user)

I also set the params so that email and password cannot be changed here. To update password and email, I link to the stock generated devise view:

edit_user_registration_path(current_user)

I admit that this is a huge work around but none of the simpler solutions solved all of the above problems.

Better and short way: add check params into the user_params block. For example:

def user_params
up = params.require(:user).permit(
%i[first_name last_name role_id email encrypted_password
reset_password_token reset_password_sent_at remember_created_at
password password_confirmation]
)


if up[:password].blank? && up[:password_confirmation].blank?
up.delete(:password)
up.delete(:password_confirmation)
end
up
end

params[:user][:password] is not working in rails 6

You have to change params[:user][:password] to params[:password]

Remove [:user] in all params

my source code is below

before run this command

rails generate devise:controllers users -c=registrations

class Users::RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]


protected


def update_resource(resource, params)
puts "params ===> #{params}"
if params[:password].blank? && params[:password_confirmation].blank?
params.delete(:password)
params.delete(:password_confirmation)
params.delete(:current_password)
resource.update_without_password(params)
else
super
end
end
end

to update attributes for user, you will need to use :current_password, to check 'do your user use correct current_password or someone wants to break your user'

so in form:

= f.input :current_password,
hint: "we need your current password to confirm your changes",
required: true,
input_html: { autocomplete: "current-password" }

in controller:

    if your_user.valid_password?(params[:user][:current_password])
your_user.update_attributes(user_params.except(:current_password, :password, :password_confirmation))
end

the first line checks 'do your user sends the correct password or not', and then we can to update the attributes without garbage

For future Googlers, I just spent 3 hours on this, here's what worked for me.

In my User model I remove :validatable and added validates method for the password and password_confirmation only if they are present:

class User < ApplicationRecord
devise :database_authenticatable, :registranable, recoverable, :rememberable
    

validates :password, length: { in: 6..128 }, if: lambda {self.password.present?}
validates_confirmation_of :password, if: lambda {self.password.present?}
end

Then in my users_controller's update method I delete the password and password_confirmation params if they are blank

class UsersController > ApplicationController
def update
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end


respond_to do |format|
if @user.update(user_params)
format.html { redirect_to @user, notice: 'User was successfully updated' }
else
format.html { render :edit }
end
end
end
end

Simple, plain, easy and no jerking around with Devise's convoluted way of doing things.

You're all welcome.

Anyone visiting in 2020, this is the simplest solution I found:

in registrations_controller.rb:

class RegistrationsController < Devise::RegistrationsController


protected


def update_resource(resource, params)
# Require current password if user is trying to change password.
return super if params["password"]&.present?


# Allows user to update registration information without password.
resource.update_without_password(params.except("current_password"))
end
end

In routes.rb:

devise_for :users, controllers: {
registrations: 'users/registrations'
}

Full credit to: Masatoshi Nishiguchi

https://www.mnishiguchi.com/2017/11/24/rails-devise-edit-account-without-password/

If you really want to update the password without the current_password then you need to use the user reset_password method

@user.reset_password(params[:user][:password], params[:user][:password_confirmation])

Here is a complete working solution to the problem that works in all scenarios:

  if params.dig(:user, :password).blank?
updated = @user.update_without_password(params[:user].to_unsafe_hash)
else
if params.dig(:user, :current_password).nil?
@user.reset_password(params[:user][:password], params[:user][:password_confirmation])


updated = @user.update_without_password(params[:user].to_unsafe_hash)
else
updated = @user.update_with_password(params[:user].to_unsafe_hash)
end


bypass_sign_in(@user)
end