Question

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
Was it helpful?

Solution 3

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.

OTHER TIPS

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.

A few years late to the party. Conditionally execute the validation based on the encrypted_password_changed? callback.

class User
  validates :first_name, :last_name, presence: true, unless: :encrypted_password_changed?
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top