10
votes

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?

7

7 Answers

17
votes

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

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

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

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

1
votes

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.

0
votes

simply override the Devise by creating app/controller/registrations_controller.rb

class RegistrationsController < Devise::RegistrationsController

  protected
  def update_resource(resource, params)
    resource.update(params.except(:current_password))
  end
end

this code will directly update user params except :current_password

and update config/routes.rb

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

My goal was to enable editing user attributes without requiring a password, unless it's changing email, password or deleting the account. And here's what worked for me:

app/controllers/registrations_controller.rb:

class RegistrationsController < Devise::RegistrationsController
  before_action :configure_permitted_parameters

  ...

  def update
    params[:user][:team_attributes][:id] = current_user.team.id
    account_update_params = devise_parameter_sanitizer.sanitize(:account_update)

    if password_required?
      successfully_updated = resource.update_with_password(account_update_params)
    else
      account_update_params.delete(:current_password)
      successfully_updated = resource.update_without_password(account_update_params)
    end

    if successfully_updated
      sign_in resource, bypass: true
      redirect_to '/'
    else
      render :edit
    end
  end

  def destroy
    current_password = devise_parameter_sanitizer.sanitize(:account_update)[:current_password]
    resource.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
    error_messages = 'Current password ' + resource.errors[:current_password].join

    if resource.destroy_with_password(current_password)
      redirect_to '/'
    else
      redirect_to delete_account_path, notice: error_messages
    end
  end

  protected

  def configure_permitted_parameters
        devise_parameter_sanitizer.permit(:account_update) do |user_params|
      user_params.permit(:username, :email, :password, :password_confirmation, :current_password, :name, :phone_number
    end
  end

  private

  def password_required?
    (resource.email != params[:user][:email] if params[:user][:email].present?) || params[:user][:password].present?
  end
end

Update config/routes.rb:

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

In views/devise/registrations/edit.html.haml

# edit form
...
= simple_nested_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { class: 'mo-form' }, defaults: { placeholder: false, hint: false }) do |f|
...

# delete form
...
= simple_form_for(resource, as: resource_name, url: user_registration_path(resource_name), method: :delete, html: { class: 'mo-form' }, defaults: { placeholder: false, hint: false }) do |f|
...