Devise 3 (rails 4) can't update user without password

11,344

Solution 1

The password validation is coming from the user model:

validates :password, presence: true

The solution is to only validate presence on create and allow_blank on update:

validates :password, presence: true, length: {minimum: 5, maximum: 120}, on: :create
validates :password, length: {minimum: 5, maximum: 120}, on: :update, allow_blank: true

Solution 2

As of 2014, you can simply override a protected method and do:

class RegistrationsController < Devise::RegistrationsController

  protected

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

Solution 3

You can use @user.update_without_password(user_params) method to update your other fields.

For example, I have this in my custom users_controller.rb. I update with remote call (ajax).

#users_controller.rb

def update
  respond_to do |format|
    if needs_password?(@user, user_params)
      if @user.update_with_password(user_params_password_update)
        flash[:success] = 'User was successfully updated. Password was successfully updated'
        format.js {render 'update'}
      else
        error = true
      end
    else
      if @user.update_without_password(user_params)
        flash[:success] = 'User was successfully updated.'
        format.js {render 'update'}
      else
        error = true
      end
    end

    if error
      flash[:error] = @user.errors.full_messages.join(', ')
      format.js {render json: @user.errors.full_messages, status: :unprocessable_entity}
    end
  end
end

private

def needs_password?(user, user_params)
  !user_params[:password].blank?
end

def user_params
  params[:user].permit(:email, :password, :password_confirmation, :username, :full_name)
end

#Need :current_password for password update
def user_params_password_update
  params[:user].permit(:email, :password, :password_confirmation, :current_password, :username, :full_name)
end

Solution 4

The key is in this "user_params[:password].blank?". The next is a example of the code:

def update
  if user_params[:password].blank?
    params = user_params_without_password
  else
    params = user_params
  end

  respond_to do |format|
    if @user.update(params)
      format.html { redirect_to @user, notice: t(:user_update) }
      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

private

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

# Never trust parameters from the scary internet, only allow the white list through.
def user_params
  params.require(:user).permit(:email, :username, :first_name, :last_name, :admin, :locked, :password)
end

def user_params_without_password
  params.require(:user).permit(:email, :username, :first_name, :last_name, :admin, :locked)
end

Hope you help

Solution 5

I went round in circles on this for ages. The answers are all in validatable as suggested by mrstif above. If you use the validatable module Devise works out of the box (with configuration options) allowing you to update user details without supplying a password so be very careful about rolling your own password validations.

Share:
11,344
Darcbar
Author by

Darcbar

Updated on July 02, 2022

Comments

  • Darcbar
    Darcbar almost 2 years

    I'm trying to update a user without having to provide a password, but approaches that worked on older devise/rails versions no longer work with devise 3 and rails 4 strong parameters.

    I'm using my user_controller to update but I have also tried using a custom devise registration controller with devise_parameter_sanitizer, without success.

    The form does not require a password (has no password field) and the user_controller handling the update looks like so:

    # PATCH/PUT /users/1
    def update
      if user_params[:password].blank?
        Rails.logger.info "entered if statement"
        user_params.delete :password
        user_params.delete :password_confirmation
        Rails.logger.info(user_params.inspect)
      end
      @user = current_user
      if @user.update(user_params)
        redirect_to @user, notice: 'User was successfully updated.'
      else
        Rails.logger.info(@user.errors.inspect) 
        render action: 'edit'
      end
    end
    
    private
    
    def user_params
      params.require(:user).permit(:screen_name, :full_name, :email, :about, 
        :location, :profile_pic, :password, :password_confirmation, :current_password)
    end
    

    .. the log after a submit looks like:

    Started PATCH "/users/13" for 127.0.0.1 at 2013-05-29 11:18:18 +0100
    Processing by UsersController#update as HTML
    Parameters: {"utf8"=>"✓", "authenticity_token"=>"20avah2OzaOVubAiam/SgvbYEQ4iijEWQqmNo7xD4rY=", "user"=>{"screen_name"=>"Darcbar", "full_name"=>"Barry Darcy", "about"=>"", "location"=>"", "website_url"=>"", "twitter_username"=>"", "email"=>"[email protected]"}, "commit"=>"Save changes", "id"=>"13"}
    User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 13 ORDER BY "users"."id" ASC LIMIT 1
    
    Entered if statement...
    {"screen_name"=>"Darcbar", "full_name"=>"Barry Darcy", "email"=>"[email protected]", "about"=>"", "location"=>"", "twitter_username"=>"", "website_url"=>""}
    
    (0.2ms)  BEGIN
    User Exists (0.8ms)  SELECT 1 AS one FROM "users" WHERE ("users"."email" = '[email protected]' AND "users"."id" != 13) LIMIT 1
    
    (0.2ms)  ROLLBACK
    #<ActiveModel::Errors:0x007fedf45bb640 @base=#<User id: 13, username: "darcbar", full_name: "Barry Darcy", about: "", location: "", email: "[email protected]", encrypted_password: "$2a$10$Mb4zsRPPqZ9CYz0zdLMBU.62NyIk/T8s6Zw/uRTwWov3...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 9, current_sign_in_at: "2013-05-28 17:51:20", last_sign_in_at: "2013-05-28 16:42:52", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", authentication_token: nil, created_at: "2013-05-27 14:03:41", updated_at: "2013-05-28 17:51:20", screen_name: "Darcbar", profile_pic_file_name: nil, profile_pic_content_type: nil, profile_pic_file_size: nil, profile_pic_updated_at: nil>, 
      @messages={:password=>["please enter a password with at least 5 characters", "please enter a password with at least 5 characters"]}>
    
    Rendered users/edit.html.haml within layouts/application (3.0ms)
    Rendered partials/head/_user_options.haml (1.8ms)
    Completed 200 OK in 74ms (Views: 12.1ms | ActiveRecord: 1.7ms)
    

    Does anyone know why the password errors are present?

  • Darcbar
    Darcbar almost 11 years
    Thanks szines, unfortunately the log displays exactly the same when using update_without_password (password errors present).
  • Zoltan
    Zoltan almost 11 years
    Much more simpler. In your case it is perfect... (Unfortunately, I have to watch that a user wants to update password or just wants to modify other details without password... But I will give a try for allow_blank...) Thanks!
  • Chris
    Chris over 10 years
    This indeed solves the problem. Keeping it DRY this seems to be enough: validates_presence_of :password, on: :create and validates_length_of :password, minimum: 5, maximum: 120, allow_blank: true. validates_presence seems to take precedence over allow blank.
  • mrstif
    mrstif over 9 years
    NOTE: This solution allows the password to be empty if you're updating it. To solve this simply do it as Devise does it in the Validatable module
  • Amin Ariana
    Amin Ariana about 9 years
    It goes in the controllers folder. It's just a regular controller, like any other.
  • Betty St
    Betty St almost 9 years
    This is also the official solution in the devise wiki: github.com/plataformatec/devise/wiki/…
  • riley
    riley almost 9 years
    Don't forget to add to your routes devise_for :users, controllers: {registrations: 'registrations'}