I'm using devise for authentication and I've added some other fields to the users table after setting up devise. User can sign-up by entering email and password only and after sign-up user can edit his profile. For that I've used :on => update. But now when I'm trying to reset the password validations are triggering error like name cannot be blank and blah blah. I'm using devise and using registrations#edit for resetting password. Below is my user model.
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :dob, :phone, :address, :state, :city, :country, :photo
has_attached_file :photo, :styles => { :small => "150x150>", :medium => "300x300>", :large => "500x500>" }
has_many :blogs, :dependent => :destroy
has_many :comments, :dependent => :destroy
has_many :followers, :through => :blogs
has_many :followings, :class_name => 'Follower', :foreign_key => 'user_id'
has_many :following_blogs, :through => :followings, :source => :blog
has_many :blog_followers, :through => :followers, :source => :user
phone_regex = /^[0-9]+$/
validates_attachment_size :photo, :less_than => 3.megabytes
validates_attachment_content_type :photo, :content_type => ['image/jpeg','image/png','image/jpg','image/gif']
validates :name, :presence => true, :on => :update
validates :dob, :presence => true, :on => :update
validates :phone, :format => { :with => phone_regex }, :length => { :is => 10 }, :on => :update
validates :address, :presence => true, :on => :update
validates :state, :presence => true,:length => { :maximum => 30 }, :on => :update
validates :city, :presence => true, :length => { :maximum => 30 }, :on => :update
validates :country, :presence => true, :length => { :maximum => 30 }, :on => :update
end
Actually, the issue is you are using validatable devise module. It puts the automatic validations on email & password even on updates. The better option would be to add your own validation on email & password on create and make these changes in the model or remove it entirely.
User Model
def email_required?
false
end
def password_changed?
false
end
Hope that will help.
Based on Sam Stickland answer...
Despite saving update_attribute(:encrypted_password, encrypted_password)
I think is better and explicit to save using save(validate: false)
.
Also you could avoid extending update controller action implementing your reset password like
def reset_password(new_password, new_password_confirmation)
byebug
if new_password.present?
self.password = new_password
self.password_confirmation = new_password_confirmation
if valid?
save
elsif errors.messages.select { |k,_v| k[/password/] }.empty?
errors.clear
save(validate: false)
else
false
end
else
errors.add(:password, :blank)
false
end
end
I had the same issue. Here's the solution I found.
It digs a bit into the devise internals so I recommend that you test this. I found I broke a lot of stuff while trying to implement this (including not requiring a matching password confirmation, or changing the password!)
Here's a gist showing the snippet of my test suite that checks password resets: https://gist.github.com/samstickland/9744ee29d0028162a7a8
The problem is that devise uses validations to check the passwords match, that it's a valid password (i.e. not too short) and that the password token is valid. This means that once you stop using the valid? method you have to instead look for particular error messages.
Firstly, you have to override the reset_password method in your user model. This new method will write the encrypted password directly (which is created as part of the password= method) if the error messages doesn't contain any 'password' errors.
The original devise implementation is found here: https://github.com/plataformatec/devise/blob/18a8260535e5469d05ace375b3db3bcace6755c1/lib/devise/models/recoverable.rb#L39 (NB: I haven't implemented after_password_reset as it's deprecated)
def reset_password(new_password, new_password_confirmation)
self.password = new_password
self.password_confirmation = new_password_confirmation
if valid?
save
elsif errors.messages.select { |k,v| k[/password/] }.empty?
# No password errors, so we write the password directly to the DB
update_attribute(:encrypted_password, encrypted_password)
true
else
false
end
end
Next you have to implement your own PasswordsController as the update method will also check to see if the errors are empty. Change it to just look for password errors. Here's mine.
I've also had to change to respond_with to a redirect_to to make the redirect work when you reset a password on an invalid model. I don't really understand why this was necessary though.
#
# This is identical to the original devise password controller except
# that it allows resetting of passwords in invalid models (i.e.
# confirmed users without a valid profile
#
class Users::PasswordsController < Devise::PasswordsController
# GET /resource/password/new
# def new
# super
# end
# POST /resource/password
# def create
# super
# end
# GET /resource/password/edit?reset_password_token=abcdef
# def edit
# super
# end
# PUT /resource/password
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
# Was:
# if resource.errors.empty?
# It's been changed to allow resetting passwords of invalid models
if resource.errors.messages.select { |k,v| k[/password/] }.empty?
resource.unlock_access! if unlockable?(resource)
if Devise.sign_in_after_reset_password
flash_message = resource.active_for_authentication? ? :updated : :updated_not_active
set_flash_message(:notice, flash_message) if is_flashing_format?
sign_in(resource_name, resource)
# Was:
# respond_with resource, location: after_resetting_password_path_for(resource)
# but this didn't seem to redirect User's with invalid attributes so I've changed
# it to a redirect
redirect_to after_resetting_password_path_for(resource)
else
set_flash_message(:notice, :updated_not_active) if is_flashing_format?
respond_with resource, location: new_session_path(resource_name)
end
else
respond_with resource
end
end
# protected
# def after_resetting_password_path_for(resource)
# super(resource)
# end
# The path used after sending reset password instructions
# def after_sending_reset_password_instructions_path_for(resource_name)
# super(resource_name)
# end
end
I'm not sure if there's a simple answer for this.
I believe it would be a good idea to somehow force the user to update those fields just after sign-up (like an account "activation"). Technically the User really is invalid if you have defined those validations.
Still, I guess you could avoid the validations with the following workaround:
Add an :unless
option (see this) to the validations:
validates :name, :presence => true, :on => :update, :unless => :resetting_password?
Then define a resetting_password?
method and make it return true when you're doing so. You could do this by setting a variable in devise's registrations controller. I think using attr_accessor
for this might suffice.
There might be better solutions, but that's what I can think right now.