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!
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
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
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.
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 🖖.
# 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With