validate email in rails after updating - ruby-on-rails

In my user model, I have
validates :email, :presence=>true,
:format => { :with => email_regex },
:uniqueness => true
In my controller, I update the email if a user chooses to change it like this:
#user.update_attribute("email","#{#new_email}")
However, it doesn't throw an error if the format is not honored.

update_attribute does no validations. use
#user.update_attributes({ :email => #new_email })
instead.

I found out that update_attribute skips checking for validation but update_attributes doesn't! Interesting.
http://apidock.com/rails/ActiveRecord/Base/update_attributes

Related

How do I get validation error for either invalid email or email field left blank?

I am trying to validate fields in a form where I want two different messages for two different problems with the input.
I have the following code:
validates_format_of :email,
:with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
:allow_blank => false
If the email field is left blank then the error message "Email is invalid" is shown in the website.
How can I get the validation to return a message saying the field cannot be blank if it is left out by the user, instead of just saying it is too short?
I like to handle this with 2 different validations (and make sure they don't both fire at the same time). So something like this:
validates_format_of :email,
:with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
:allow_blank => true
validates_presence_of :email
The validates_presence_of handles making sure the email is not blank. And changing validates_format_of to use :allow_blank => true will make sure the formatting validation won't run if the email is blank.
In Rails 3.0+ you can also combine the two validations into a single one using validates:
validates :email,
presence: true,
format: { with: /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
allow_blank: true }
You create a separate validation for the blank case:
validates_presence_of :email
validates_format_of :email,
:with => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
:allow_blank => true
You need to add another validation checking for presence separately, like:
validates :email, presence: true, format: { with: YOUR_REGEX }

How to have exception validation in Rails

I'm a newbie in rails and I'm stuck with this problem: I have a model named User
class User < ActiveRecord::Base
attr_accessor :password
EMAIL_REGEX = /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i
validates :first_name, :presence => true,:format => /\A[a-zA-Z]+\z/
validates :last_name, :presence => true,:format => /\A[a-zA-Z]+\z/
validates :email, :presence => true, :uniqueness => true, :format => EMAIL_REGEX
validates :password, :presence => true
validates_length_of :password, :in => 6..20, :on => :create
end
with database attributes first_name, last_name, email, hashed_password and encrypted_password.
When I create new Object of User and saves it in the database there is no problem. NOW, here's the problem I want to edit attributes of my User Object EXCEPT email and password.
Once I try to edit the record through edit of rails resource it flags an error that password should not be empty. I am planning to have an exemption of validation if the user wants to edit his/her information but I know that it is not a good practice.
Hoping to find the best answer.
For starters check out http://guides.rubyonrails.org/active_record_validations.html#conditional-validation - once this approach becomes unDRY (repeated more then 2-3 times) see http://apidock.com/rails/Object/with_options
Once you get more advanced you will want to try something in the lines of a form/service class and extract form/action-specific validations there - http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
...and welcome to rails :)
Try using
validates :password, :presence => true, on: :create
And in the user edit form add every field except email and password.
But thing is that while you are going to change the password, there is no validation on password. So when you are implementing the change_password section, you need handle it and nee to add the errors manually for the password.
Problem Solved! Im not sure if this is the best solution. Once I instantiate a User object I use the after_initialize then set updatePassword to true as a default value then in the edit action of User Object I set updatePassword to false so that it will be exempted in the validation. I also use the conditional validation for the updatePassword. :D Thanks for all the ideas!

Applying validation exception when using state_machine

I have been unable to find a precise way to accomplish a validation exception on my rails model when using state_machine.
I have as expected a state column in my model and process a validation of emails by passing a user from unverified to verified, simple enough.
My model contains a normal validation of passwords
validates :password, length: {minimum: 6}
validates :password_confirmation, presence: true
This validation is important, but when passing my user from an unverified to a verified state I run into problems with my model validation as I am only applying an update to a single column, but active record integration with state_machine seems to apply a record update.
The state_machine snippet:
state_machine :initial => :unverified do
event :verify do
transition :unverified => :verified
end
So the solution would seem to apply an exception to the model validation, I was drawn to :unless as a solution..
I implemented something that I have to say I did not understand fully, which seems to now have the effect of removing any validation on password, obviously making my state_machine process work, but not meeting my objective..
my changes where to add:
validates :password, length: {minimum: 6}, :unless => :proc_no_password_validation
validates :password_confirmation, presence: true, :unless => :proc_no_password_validation
and apply in a private method:
def proc_no_password_validation
Proc.new { |user| user.verify }
end
I have a feeling that directionally I am on the right track, but help to spot my mistake or issue or other solution would be much appreciated.
UPDATE
So I have had some help from Jef on this, but my conclusion was that the model validation method exception route was wrong, I modified my state code as follows:
state_machine :state, :initial => :unverified, :action => :bypass_validation do
event :verify do
transition :unverified => :verified
end
event :unverify do
transition :verified => :unverified
end
end
and then a validation bypass on the state I am interested in ignoring
def bypass_validation
if self.changes['state'][1] == 'verified'
save!(:validate => false)
else
save!(:validate => true)
end
end
:unless accepts a method name as a symbol or a proc. You gave the name of a method that returns a Proc. As a Proc is not false, your unless condition is met and your validation skipped.
You should use one of :
validates :password_confirmation, presence: true, :unless => :proc_no_password_validation
def proc_no_password_validation
self.verify
end
validates :password_confirmation, presence: true, :unless => Proc.new{|user| user.verify}
UPDATE
state_machine allows you to wrap your validations into states :
state :unverified { # or :verified, when should validation apply ?
validates :password ...
validates :password_confirmation ...
}

attr_accessor and password validation on update

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

Nested model validation context

I am using Ruby on Rails 3.0.9 and I am trying to validate a nested model in a specific context just for the email attribute uniqueness.
In my controller I have:
#user.valid? :uniqueness_context
In my nested model I have:
validates :email,
:format => {
:with => EMAIL_REGEX
},
:uniqueness => {
:on => :uniqueness_context # Here it doesn't work
},
:presence => true
What is wrong? How can I make the above validation code to work?
Notice: if in the model I use the following:
validates :email,
:format => {
:with => EMAIL_REGEX
},
:uniqueness => true,
:presence => true
all works as expected.
In order to solve the issue I have tried also to use the following in the model:
validates :email,
:format => {
:with => EMAIL_REGEX
},
:presence => true
validates_uniqueness_of :email, :on => :uniqueness_context
but it still doesn't work.
I ran into the same problem. Seems that Rails currently does not support custom validation contexts. :if will do the job for you.
Sorry, I spaced it for a minute because I didn't realize you could create a custom context.
Looking at the source, it doesn't appear that the UniquenessValidator supports the :on context option.
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/validations/uniqueness.rb

Resources