In my User model I set validations for the password and its confirmation:
validates_presence_of :password, :password_confirmation
I use this for the forms. These attributes are not stored in the database, I am using the authlogic gem.
Now I am trying to make a system to restore forgotten passwords via email. The user inputs his email and the object is retrieved like this:
#user = User.find_by_email(params[:email])
Then, at a certain point in the process the value of a User attribute is set to true meaning that the user forgot his password, so I need to do a #user.save.
And there's the problem. Since password and password_confirmation were never stored, their values are nil. However, the validations come in play and prevent the user from being updated.
Is there any way to skip those particular validations so I can make this work? Or should I do something else? Any help?
You can skip all validations with save(validate: false). You can skip individual validations with the :if or :unless option, for example.
validates_presence_of :password, :password_confirmation, unless: :forgot_password?
def forgot_password?
# return true if the user is in the forgot password process
end
change
validates_presence_of :password, :password_confirmation
to
Assuming your field that is set to true is forgot_password
validates_presence_of :password, :password_confirmation, :unless => Proc.new { |a| a.forgot_password? }
Conditional Validations documentation
Related
I have working on a Ruby on Rails application and have a problem. I've user model in my domain and it has a has_secure_password field like this:
class User < ApplicationRecord
validates :name, presence: true
has_secure_password
validates :password, presence: true
validates :password, length: { in: 6..20, message: 'Password shoul be minimum 6 characters'}
end
And I save the user object with this function:
def self.create_with_params(email,name,password)
user = User.new()
user.provider= :mail
user.name=name
user.email=email
user.password=password
user.activate_status=false
user.save(validate: true);
return user;
end
That's ok too.
But when I want to update a field in my user model. I get validation error for password field.
#user = User.find_by(email: params[:email]) # this record has a valid password_digest field in database.
#user.name = 'John'
#user.save
puts #user.errors.full_messages
It prints:
"Password shoul be minimum 6 characters".
Note: I have password_digest field in my users table.
I think when I query users table password_digest field couldn't be filled in to password field on model.
Any suggestions?
I think when I query Users table password_digest field couldn't be
filled in to password field on model.
Correct, the password is not stored, so when you query User you don't get the password field, yet your model asks for it every time with:
validates :password, presence: true
So, you will need to provide password before saving #user to make it work, but i guess that is not what you want, so just remove that validation from your model,
You won't need to validate password every time, just upon creating, and that validation is done with has_secure_password.
Also, for the same reason, you will need to update length validation to:
validates :password, length: { in: 6..20, message: 'Password shoul be minimum 6 characters'}, allow_nil: true
I have this validation for my user:
validates :password,
presence: true,
confirmation: true,
length: { minimum: 6 },
:on => :create
This is obvious. When I'm creating (registering) a new user, I want to fill up their password hence that's why presence: true.
Case 1) When the user wants to update his account (lets say change his username), the form has only the email and username fields. That's ok and the validation is ok.
Case 2) He forgot his password and I send him his "forgotten password link" and he is on the page where he is creating his new password. The form has these two fields: password and password confirmation. However, he leaves both of these fields empty and submits the form. The validation passes because it's only :on => create! because of case 1)
I can not add :on => update because the case 1) wouldn't pass, because there is no password field.
What should I do in this situation? What is the best practice or what is the real word solution to this "problem"?
What I have done for this situation is instead of using on: :create, I use a virtual attribute that I set only when setting/changing the password. Something like this:
validates :password, if: :changing_password?
attr_accessor :password_scenario
def changing_password?
self.password_scenario.present?
end
Then in your controller, you would simply set password_scenario to true whenever you are requiring password to be present.
I am working on a project and need some help on where to begin. I have three pages
Update User
Create User
Admin User Password Change (like a Hard Reset Password for but only the admin can reset the user's password)
Change Password
On Create User first name, last name, username, password, and password confirmation are mandatory.
On Update User just first name, last name and username are mandatory.
On Admin User Password Change and Change Password, just password and password confirmation are mandatory.
How would you go about doing this? I don't think this is possible through models using validates_presence_of with an if because there are too many scenarios. Any help or guidance would be appreciated. Also, I am pretty new to Rails if you can't already tell.
You can pass conditionals to your validations:
validates :password, :confirmation => true, :presence => true
validates :first_name, :last_name, :username, :presence => true
validate :admin_user_password_change?
Of course you'd have to define what the admin_user_password_change? method would be to determine if it is an admin user changing a password.
UPDATE
The admin_user_password_change? method might be something like:
def admin_user_password_change?
unless self.admin? && self.password.present? && self.password_confirmation.present?
self.errors.add(:admin_password_change, "password and password_confirmation are required.")
end
end
As for How would it communicate with the controller?, it wouldn't directly. But if any of the conditions in the method are false (e.g. self.admin? && self.password.present? && self.password_confirmation.present?), an error will be added to the instance of User and the instance won't save in the controller.
Setting some fields to new values doesn't unset other fields; just because you're only updating some fields in one action doesn't mean the other fields will be unset, so long as they start in a consistent state.
Just add your validations. It will work fine.
You can tell to your validation work only on certain cenarios only using:
The create:
validates :first_name, :last_name, :username, presence: true, on: :create
The update:
validates :password, presence: true, on: :update
Take a look at on.
For validation based on context take a look at Context Validations
I'm not sure on how to best tackle this issue - I need to validate a user's password when adding a new record and I also need to be able to validate when updating the passoword too. But how can I let a user update just part of their profile and perhaps leaving the password blank.
Note before you suggest allow_blank I am aware of this option but this is not suitable because when a user needs to change their password as a result of losing/forgetting it I don't want to allow user to have a blank password.
validates :password, presence: true, if: lambda { |user| user.password_changed? }
by ActiveModel::Dirty (available on all models by default)
You can pass an if statement to a validation:
`validates_presence_of :password, :if => :should_validate_password?
You should be able to pass a condition in here to catch whether the user is updating their password or not:
Model
def should_validate_password?
updating_password || new_record?
end
Controller
#user.updating_password = true
#user.save
See here for more details: http://railscasts.com/episodes/41-conditional-validations
Updated:
In your case I would create an if statement in the controller that detected whether any new password was being passed in the params. If so I would set #user.updating_password = true which would trigger the validation in the model. If not then #user.updating_password would be nil and the validation wouldn't trigger.
Fully custom way
Usually, #password= and #password_confirmation= are just virtual setters, the true attribute is #hashed_password or something. So, you could do something like this :
class User
attr_accessor :password, :password_confirmation
validate :validates_password
validates_presence_of :hashed_password
private
def validates_password
if password or password_confirmation
if password != password_confirmation
errors.add( :password, 'your message' )
end
# your others validations
self.hashed_password = hash_password
end
end
def hash_password
# your hashing code
end
end
When user is created providing password, virtual attributes password and password_confirmation are set, so if condition is true and validations are enforced.
When password is already set and user does not change it (an edit form without providing passwords), validation will not be enforced, because of if password and password_confirmation.
When password is already set and user change it, password and password_confirmation are set, so validation is triggered.
When you want to reset password, just set in your action hashed_password to nil. The model is now invalid because of validates_presence_of :hashed_password and user has to provide a new one.
Using #has_secure_password
With #has_secure_password, rails will handle most of this, especially :
it creates virtual atttributes
it will trigger validation for confirmation match only if password attributes are provided
it will hash password
So, what you need is just adding your own validations, doing it only when password or password_confirmation are present.
class User
has_secure_password
validate :validates_password
private
def validates_password
if password or password_confirmation
unless <your_test>
errors.add( :password, '<your error message>' )
end
end
end
end
As previously, this will only be triggered if password and password_confirmation are provided, which only happens if user submitted them as form data (as real attribute is password_digest).
My User model contains :name, :email, and :password fields. All 3 have validations for length. An "update account" web page allows the user to update his name and email address, but not password. When submitted, params[:user] is
{"name"=>"Joe User", "email"=>"user#example.com"}
Note there is no "password" key because the form doesn't contain such an input field.
When I call
#user.update_attributes(params[:user])
the password validation fails. However, since I'm not attempting to update the password, I don't want the password validation to run on this update. I'm confused why the password validation is running when params[:user] doesn't contain a "password" key.
Note that I want to have a separate web page elsewhere that allows the user to update his password. And for that submission, the password validation should run.
Thank you.
My application does something like this
attr_accessor :updating_password
validates_confirmation_of :password, :if => should_validate_password?
def should_validate_password?
updating_password || new_record?
end
so you have to model.updating_password = true for the verification to take place, and you don't have to do this on creation.
Which I found at a good railscast at http://railscasts.com/episodes/41-conditional-validations
In your user model, you could just ignore the password validation if it's not set.
validates_length_of :password, :minimum => N, :unless => lambda {|u| u.password.nil? }
Using update_attributes will not change the value of the password if there is no key for it in the params hash.
Validation doesn't run against the changed fields only. It validates existing values too.
Your validation must be failing because the password field contains some invalid content that's already saved in the database. I'm guessing it's probably because you're hashing it after validation and you're trying to validate the hashed string.
You can use a virtual attribute (an instance variable or method) that you validate with a custom method, and then assign the hash to the stored password field. Have a look at this technique for ideas.
An app that I am working on uses the following:
validates_confirmation_of :password,
:if => Proc.new { |account|
!account.password.blank?
|| !account.password_confirmation.blank?
|| account.new_record? }
Depending on your requirements, you might want to remove the new_record? check
When password is added then only confirmation will be called and presence will call on create action only
**validates_presence_of :password, :on =>:create**
**validates_confirmation_of :password**