I have this code in my user model:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
before_save :encrypt_password
validates :email, :presence => true,
:uniqueness => { :case_sensitive => false },
:format => { :with => /\A[^@]+@[^@]+\z/ },
:length => 7..128
validates :password, :presence => true,
:confirmation => true,
:length => 6..128
private
def encrypt_password
return unless password
self.encrypted_password = BCrypt::Password.create(password)
end
end
Now in my controller when I'm updating some user fields with
@user.update_attributes(params[:user])
the password field is always validated, even when it is not set in the params hash. I figured that this is happening because of the attr_accesor :password which always sets password = "" on update_attributes.
Now I could simply skip the validation of password if it is an empty string:
validates :password, :presence => true,
:confirmation => true,
:length => 6..128,
:if => "password.present?"
But this doesn't work because it allows a user to set an empty password.
Using update_attribute on the field I'd like to change is not a solution because i need validation on that attribute. If I pass in the exact parameter with
@user.update_attributes(params[:user][:fieldname])
it doesn't solve the problem because it also triggers password validation.
Isn't there a way to prevent attr_accesor :password from always setting password = "" on update?
New answer
This works for me:
validates :password, :presence => true,
:confirmation => true,
:length => { :minimum => 6 },
:if => :password # only validate if password changed!
If I remember correctly it also took me some time to get this right (a lot of trial and error). I never had the time to find out exactly why this works (in contrast to :if => "password.present?"
).
Old answer - not really useful for your purpose (see comments) I get around this problem by using a completely different action for password update (user#update_password). Now it is sufficient to only validate the password field
:on => [:create, :update_password]
(and also only make it accessible to those actions).
Here some more details:
in your routes:
resources :users do
member do
GET :edit_password # for the user#edit_password action
PUT :update_password # for the user#update_passwor action
end
end
in your UsersController:
def edit_password
# could be same content as #edit action, e.g.
@user = User.find(params[:id])
end
def update_password
# code to update password (and only password) here
end
In your edit_password view, you now have a form for only updating the password, very similar to your form in the edit view, but with :method => :put and :url => edit_password_user_path(@user)
The solution I have started using to get round this problem is this:
Start using ActiveModel's built in has_secure_password
method.
At console
rails g migration add_password_digest_to_users password_digest:string
rake db:migrate
In your model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :login_name, :password, :password_confirmation
# secure_password.rb already checks for presence of :password_digest
# so we can assume that a password is present if that validation passes
# and thus, we don't need to explicitly check for presence of password
validates :password,
:length => { :minimum => 6 }, :if => :password_digest_changed?
# secure_password.rb also checks for confirmation of :password
# but we also have to check for presence of :password_confirmation
validates :password_confirmation,
:presence=>true, :if => :password_digest_changed?
end
And finally,
# In `config/locales/en.yml` make sure that errors on
# the password_digest field refer to "Password" as it's more human friendly
en:
hello: "Hello world"
activerecord:
attributes:
user:
password_digest: "Password"
Oh, one more thing: watch the railscast
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