Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating `User` attributes without requiring password

Right now, users can edit some their attributes without having to enter their password because my validations are set up like this:

validates :password, :presence =>true, :confirmation => true, :length => { :within => 6..40 }, :on => :create
validates :password, :confirmation => true, :length => { :within => 6..40 }, :on => :update, :unless => lambda{ |user| user.password.blank? } 

However, after a user does this, their password is deleted - update_attributes is updating their password to "". Here is my update definition:

def update

    if @user.update_attributes(params[:user])
        flash[:success] = "Edit Successful."
        redirect_to @user
    else
        @title = "Edit user"
        render 'edit'
    end
end

I've also tried using a different definition that uses update_attribute instead:

def save_ff
    @user = User.find(params[:id])
    @user.update_attribute(:course1, params[:user][:course1] )
    @user.update_attribute(:course2, params[:user][:course2] )
    @user.update_attribute(:course3, params[:user][:course3] )
    @user.update_attribute(:course4, params[:user][:course4] )
    redirect_to @user 
end 

But for some reason this is doing the same thing. How can I update some user attributes without changing the password? Thanks!

like image 763
steffi2392 Avatar asked Aug 16 '11 19:08

steffi2392


5 Answers

I didn't realize the solution I gave you yesterday would lead to this problem. Sorry.

Well, taking inspiration from devise, you should simply update your controller this way:

def update
  params[:user].delete(:password) if params[:user][:password].blank?
  if @user.update_attributes(params[:user])
    flash[:success] = "Edit Successful."
    redirect_to @user
  else
    @title = "Edit user"
    render 'edit'
  end
end
like image 93
apneadiving Avatar answered Nov 02 '22 21:11

apneadiving


This blog post demonstrates the principal of what you want to do.

What is not shown, but may be helpful, is to add accessors to the model:

attr_accessor   :new_password, :new_password_confirmation
attr_accessible :email, :new_password, :new_password_confirmation

and to provide all of the desired validation under the condition that the user has provided a new password.

  validates :new_password,  :presence => true, 
                            :length   => { :within => 6..40 }, 
                            :confirmation => true, 
                            :if       => :password_changed?

Lastly, I would add a check to see if the encrypted_password has been set in order to determine if "password_changed?" in order to require a password on a new record.

  def password_changed?
    !@new_password.blank? or encrypted_password.blank?
  end
like image 28
cbedrosian Avatar answered Nov 02 '22 19:11

cbedrosian


I've been struggling with this and going around in circles for a while, so I thought I'd put my Rails 4 solution here.

None of the answers I've seen so far meet my use case, they all seem to involve bypassing validation in some way, but I want to be able to validate the other fields and also the password (if present). Also I'm not using devise on my project so i can't make use of anything particular to that.

Worth pointing out that it's a 2 part problem:

Step 1 - you need to remove the password and confirmation field from the strong parameters if the password is blank like so in your controller:

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

Step 2 - you need to alter validation such that the password isn't validated if it's not entered. What we don't want is for it to be set to blank, hence why we removed it from our parameters earlier.

In my case this means having this as the validation in my model:

validates :password, :presence => true, :confirmation => true, length: {minimum: 7}, :if => :password

Note the :if => :password - skip checking if the password is not being set.

like image 11
Richard W Avatar answered Nov 02 '22 20:11

Richard W


2017 answer:

In Rails 5 as also indicated by Michael Hartl's tutorial, it's enought that you have something along these lines in your model:

validates :password, presence: true, length: { minimum: 6 }, allow_nil: true

allow_nil: true is the key here which allows a user to edit his/her info without also requiring a password change too.

At this point one might think that this will also allow empty user signups; However this is prevented by using the has_secure_password which automatically validates password presence exclusively in the create method.

This is a demo User model for illustration purposes:

class User < ApplicationRecord
  attr_accessor :remember_token
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
end

Unfortunately I've no clue how to do make this work in devise. Cheers 🖖.

like image 5
stratis Avatar answered Nov 02 '22 20:11

stratis


# It smells

def update
  if params[:user][:password].blank?
    params[:user].delete :password
    params[:user].delete :password_confirmation
  end

  if @user.update_attributes(params[:user])
    flash[:success] = "Edit Successful."
    redirect_to @user
  else
    @title = "Edit user"
    render 'edit'
  end
end

# Refactoring

class User < ActiveRecord::Base
  ...
  def update_attributes(params)
    if params[:password].blank?
      params.delete :password
      params.delete :password_confirmation
      super params
    end
  end
  ...
end

def update
  if @user.update_attributes(params[:user])
    flash[:success] = "Edit Successful."
    redirect_to @user
  else
    @title = "Edit user"
    render 'edit'
  end
end

# And little better

class User < ActiveRecord::Base
  ...
  def custom_update_attributes(params)
    if params[:password].blank?
      params.delete :password
      params.delete :password_confirmation
      update_attributes params
    end
  end
  ...
end

def update
  if @user.custom_update_attributes(params[:user])
    flash[:success] = "Edit Successful."
    redirect_to @user
  else
    @title = "Edit user"
    render 'edit'
  end
end
like image 6
fl00r Avatar answered Nov 02 '22 21:11

fl00r