It seems like this should be straightforward. When signing up new users, I want custom errors for blank user names and passwords. It worked fine for the user name:
validates :name, presence: { message: "Please enter a name." },
length: { maximum: 50,
message: "Please enter a name shorter than 50 characters"}
When the field is blank, it gives the "Please enter a name." error.
I the same thing for the password:
has_secure_password
validates :password, presence: { message: "Please enter a password." },
length: { minimum: 8,
message: "Please choose a password with at least 8 \
characters."}
The minimum length message works fine. But if I submit with an empty password, I get the default "can't be blank" message.
has_secure_password automatically adds some validations for you:
Password must be present on creation
Password length should be less than or equal to 72 characters
Confirmation of password (using a password_confirmation attribute)
To prevent that, declare:
has_secure_password validations: false
And then you can add your own.
has_secure_password validations: false worked at first, but I had a problem when I made the edit user profile page. It assumes the user might not enter a password. So it depends on the validations in has_secure_password, which the answer above disables.
A better way is to edit config/local/en.yml:
en:
activerecord:
attributes:
user:
password: "Password"
errors:
models:
user:
attributes:
password:
blank: "Please enter a password."
Related
Faced a problem not clear to me.
I used password validation during registration.
def pass_val
if password_digest.count("a-z") <= 0 || password_digest.count("A-Z") <= 0
errors.add(:password, "must contain 1 small letter, 1 capital letter and minimum 8 symbols")
end
end
validates :password_digest, presence: true,
length: { minimum: 8 }
I made it possible to recover the password for the user, and validation just stopped working.
I changed all password_digest in validation to just password and validation worked again.
BUT, after that, password recovery disappeared, which began to curse for validation.
NoMethodError in PasswordResetsController#create
undefined method `count' for nil:NilClass
If I change all password again to password_digest, validation will disappear, but the recovery password will work again.
UPDATE
Using password - validation in the console works (it also works on the site. Passwords recovery does not work).
Using password_digest - validation in the console also works (it doesn’t work on the site, the recovery password works).
UPDATE 2
Moreover, an error occurs during password recovery when a letter is sent to the mail (before sending), and not exactly during the password change.
UPDATE 3
create method in PasswordResetsController:
def create
author = Author.find_by_email(params[:email])
author.send_password_reset if author
flash[:success] = "Email sent with password reset instructions."
redirect_to root_path
end
UPDATE 4
send password_reset:
def send_password_reset
confirmation_token
self.password_reset_sent_at = Time.zone.now
save!
AuthorMailer.password_reset(self).deliver!
end
But the letter is not sent, crashes up to this point.
I want to emphasize that I either use password_digest in validation and validation does not work, and password recovery works. Or I use password in validation and validation works, but password recovery does not.
Also, it’s worth noting that the create method in PasswordResetsController is not used to change the password, but to send an email to the email. I can’t understand why validation swears at him.
Seems like you already found a solution yourself. However I'm not sure you understand why this is happening.
Let's start of with the important fact that password_digest doesn't contain the password but rather a hashed version of the password. (The result of a hash function is also called a digest.) You'll want to add the validations to the password attribute, since that contains the actual password.
When you assign a new password to an author both the password and password digest will be assigned a new value. You could imagine the password setter to look something like this:
def password=(password)
#password = password
#password_digest = do_some_hash_magic(password)
end
Because password is set when you assign it to the author you're able to validate the contents of it.
Another important thing to know is that the actual password is never saved into the database, only the password digest is (for security reasons). For this reason when you (re)load the instance from the database password is set to nil, because the application no longer knows what it is.
Instead of skipping the validation with save(validate: false), I suggest using the :allow_nil option or making the validation conditional to only trigger the validation when the password is not nil.
validates :password, length: { in: 8..255 }, allow_nil: true
validates :password, format: { with: /\p{Lower}/, message: :no_lower }, allow_nil: true
validates :password, format: { with: /\p{Upper}/, message: :no_upper }, allow_nil: true
validates :password, format: { with: /\p{Digit}/, message: :no_digit }, allow_nil: true
validates :password, format: { with: /[\p{S}\p{P}]/, message: :no_special }, allow_nil: true
Alternatively you can be more explicit and use the :on option with a custom context to only trigger trigger the validations when you want them to:
validates :password, length: { in: 8..255 }, on: :password
validates :password, format: { with: /\p{Lower}/, message: :no_lower }, on: :password
validates :password, format: { with: /\p{Upper}/, message: :no_upper }, on: :password
validates :password, format: { with: /\p{Digit}/, message: :no_digit }, on: :password
validates :password, format: { with: /[\p{S}\p{P}]/, message: :no_special }, on: :password
The context doesn't have to be named after an attribute, using :foo instead of :password would also be just fine. You can then run these validations by passing a context when calling valid? or save.
author.valid?(:password)
author.save(context: :password)
If you use Rails 5 or higher you can pass multiple contexts as an array if you want to run a mix of validations. author.valid?([:create, :password])
For additional info about the regexes used check out the Ruby regexp documentation.
The error was in the send_password_reset method.
Added (: validate => false) to this method and it worked.
def send_password_reset
confirmation_token
self.password_reset_sent_at = Time.zone.now
save!(:validate => false)
AuthorMailer.password_reset(self).deliver!
end
In vallidation i use password.
I added password password validations to my User model:
validates :password, presence: true
validates :password, confirmation: { case_sensitive: true }
but then when I wanted to update other fields on users, those validations were rendering the transaction invalid, as password was not present.
Through a bit of research, I realised that I could skip those validations if password wasn't present:
validates :password, presence: true, if: :validate_password?
validates :password, confirmation: { case_sensitive: true }, if: :validate_password?
def validate_password?
password.present? || password_confirmation.present?
end
However, now when I submit a blank password and password confirmation, validate_password? returns false. I don't really understand what's happening, because
#user.update_attributes(password_reset_edit_params) returns true
where
password_reset_edit_params is <ActionController::Parameters {"password"=>"", "password_confirmation"=>""} permitted: true>
but inside
def validate_password?
password.present? || password_confirmation.present?
end
password and password_confirmation evaluate to nil, and my #user password doesn't update to an empty string.
I should mention that I'm using Bcrypt, and #user.password actually will always evaluate as nil, whereas a password_digest is available.
So what is the solution? Ultimately I guess my question is simply:
How can ignore password validations when I'm not attempting to submit a password, but also permit those validations upon submission of an empty string?
It occurs to me that I could add a condition in the controller that doesn't allow empty strings to be passed, but there's gotta be a neat way to fix this issue.
Any help is much appreciated. Thanks!
If you use Bcrypt and digest, you can use in your model for example
has_secure_password
validates :password, length: { minimum: 8 }, allow_blank: true
In this case validation will work only for setting and changing password.
If you don't change password you don't need to enter password.
Another way
validates :password, presence: true, on: :create
In Rails, you are not validating the param, you are validating the model.
Do not "skip validation if the password is not present". That's the purpose of validation. Validation is meant to validate the model, not the params. If the model has an empty password, and the password's presence is required, it is an invalid model. Always.
but then when I wanted to update other fields on users, those validations were rendering the transaction invalid, as password was not present.
It is not because the password is not present, it is because the instance of the User does not have a password in it. The way it should work is:
User is created with password and validated: User.create(name: "Me", password: "something that isn't nil")
User is updated: User.update(name: "New name") # password is inside the model still so it is still valid
That's how it works. If you want a user to be able to have a nil / empty password, then you should not attempt to place the presence validation in the model.
Ultimately I'm just validating password formatting and confirmation on the User model:
validates :password, confirmation: true,
length: {within: 6..40},
allow_blank: true
and sanitising input data against blank password / password_confirmation in the controller.
if password_reset_edit_params[:password].blank? || password_reset_edit_params[:password_confirmation].blank?
#user.errors[:password] << "can't be blank" if password_reset_edit_params[:password].blank?
#user.errors[:password_confirmation] << "can't be blank" if password_reset_edit_params[:password_confirmation].blank?
render 'edit'
I have a model User.
Here're a few validation fields:
validates :first_name, presence:{ message: "please add your First name"}
validates :last_name, presence:{ message: "please add your Last name"}
validates :username, presence:{ message: "please pick a username"},
When I try to sign up using devise, I have a message that I should provide first name and last name. But for registration I only want to use 3 fields shown: username, email, password.
How to skip validation of first and last names during registration?
Thanks.
You don't want the validations for first_name, last_name to run while creating a new user BUT you want them while updating an existing user record. All you need to do is , modify the validations such that they will only be run while updating a user record.
You can do this by using on: :update option as below:
validates :first_name, presence:{ message: "please add your First name"}, on: :update
validates :last_name, presence:{ message: "please add your Last name"}, on: :update
I'm a bit confused. Before I added email validation functionality to my sample app, my validation looked like this in the /app/models/user.rb:
validates :password, length: { minimum: 6 }
Once I implemented my signup email confirmation functionality, I needed to add this to the validation:
validates :password, length: { minimum: 6 }, if: :validates_password?
...
def validates_password?
password.present? || password_confirmation.present?
end
because I would get a validation error when the user clicked on the link inside the email to verify their email address. Upon making this change to the password validation, my email worked! However, my test for the invalid information in my edit action of the users controller began to fail. Then I noticed that the edit form no longer has an error even though they do not type the password in. So I added some debug statements that you can see in the image below:
"user password present" is evaluated as #user.password.present?
"user password_confirmation present" is evaluated as #user.password_confirmation.present?
"user is valid" is evaluated as #user.valid?
"validate_password?" is evaluated as #user.password.present? || #user.password_confirmation.present?
How is this happening? How is my user valid by passing valid? but the validate_password? method is returning false???
Can someone help me please?
From the comments:
validates :password, length: { minimum: 6 }, if: :validates_password?
is saying "I'm going to make sure the password length is a minimum of 6 characters if validates_password? is true.
If the user doesn't fill in password/password_confirmation, then validates_password? is false so:
validates :password, length: { minimum: 6 }, if: :validates_password?
never fires. That's what is happening in this case.
I'm using devise as authentication engine in my app.
Is there any way to use custom messages when devise validation fails.
Devise provides me with the following message when the password is blank: Password can't be blank, but i need another message. How can i do it?
ActiveRecord en.yml is the answer I would suggest if you want to change the Validation Message for Devise
Here is how the en.yml should look like
en:
activerecord:
errors:
models:
user:
attributes:
email:
blank: "Please Specify an Email id"
taken: "Please use a different Email id"
invalid: "Please Specify a valid Email id"
password:
blank: "Please Specify a Password"
confirmation: "Password does not match"
password_confirmation:
blank: "Please Specify a Password Confirmation"
first_name:
blank: "Please Specify First Name"
last_name:
blank: "Please Specify Last Name"
pdf:
attributes:
name:
blank: "Please Specify name to PDF"
taken: "Please use different name for PDF"
attachment:
blank: "Please Upload a PDF Attachment"
data_element:
attributes:
name:
blank: "Please give Element a desired name"
taken: "Already Created Element with given name"
color:
blank: "Please assign a color to Element"
template:
attributes:
name:
blank: "Please Specify a Name"
taken: "Please use a different name"
I advice you to define this way instead of customizing devise validation module
Because if you follow the above approach, it would be possible that you would skip a validation a place or two
for Example I the remove the above devise validation module and then substitue your own in
User Model
then all the validation would work for but you would miss the validation in Change Password
There by resulting your to login even though the password was never supplied and never given
Keep a loop of that too
Cheer
Regards
Please refer to the URL below.
http://railscasts.com/episodes/210-customizing-devise?view=asciicast
If the user is signing in, you can edit all the error messages in devise.en.yml under config/locales.
If you're signing up, Devise provides its own validations out of the box without any customizing. If you want to customize it, you can edit the User model.
Find devise :validatable and remove the :validatable option. After which, you should be able to use the usual rails validations. Note that this will cause you to have to do all the validations yourself.
validates_presence_of :password, :message=>"Your custom error message!"
Some usual validations:
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
Not a complete answer, but this sounds like it should be solvable with I18n, either with the devise-internal keys, or by overriding active record's validation error messages for your user model.
Here's a similar question: Devise attributes for i18n?
You can customize your devise messages from config/locales/devise.en.yml but if you want to change to validation message then delete :validatable from Model. Then you can change a validation message like before.
For example:
validates_uniqueness_of :email, :case_sensitive => false, :allow_blank => true, :if => :email_changed?
validates_format_of :email, :with => Devise.email_regexp, :allow_blank => true, :if => :email_changed?
validates_presence_of :password, :on=>:create
validates_confirmation_of :password, :on=>:create
validates_length_of :password, :within => Devise.password_length, :allow_blank => true