Search code examples
ruby-on-railsrubydevise

Rails + Devise: Update user with or without pasword


There's a ton of related questions:
- Devise: Update Account without password confirmation
- rails: devise update user without password
- Devise 3 (rails 4) can't update user without password
- etc...

But when I tried to work with their answers I never ended up getting what I wanted so I've decided to post this Q&A style.

Situation:
I was working on a Rails app using devise and I wanted a "Manage Profile form." I wanted one form for updating user information, including email and password, but also first_name, last_name, and other non-devise fields. I only wanted to require the password fields (password and password_confirmation) if the user was trying to change their password. I also wanted to build my form with the form_for helper, and whitelist the fields the user is able to update. And I also wanted all the handy error messages from devise (like, "Your password must be at least 8 characters." or whatever they are).


Solution

  • TL;DR: Strip params to only be what you want, I call this user_params. In the action that processes the form check if user_params[:password] is empty, and if it is try to update your @user model with @user.update_without_password(user_params) if it isn't try to update with @user.update(user_params). If the applicable update call returns false @user.errors holds the explanation.

    Here's exactly how I solved this problem:
    I defined a resource in my config/routes file:

    resource :profile  
    

    I made a controller for my resource that extends devise's authenticated controller ProfilesController < AuthenticatedController for managing profiles. Profiles controller contains several methods including user_params which mainly filters params down to a whitelist:

    def user_params
      accessible = [
        :first_name,
        :last_name,
        :email,
        :password,
        :password_confirmation
      ]
      params.require(:user).permit(accessible)
    end
    

    and update which does the business of processing the form:

    def update
      # current_user holds the logged in user
      @user = current_user
      # IF they've left the password field blank, 
      # AND the devise update_without_password method returns true
      # OR IF a full update of user (including password and password_confirmation) returns true
      # THEN re-sign them in to flush their session, and redirect them back to their dashboard, and send a success message.
      # ELSE re-present the edit form they were just on (there's a handy catcher
      # in the edit view script to render the form errors, you can find them on
      # @user.errors)
    
      if (user_params[:password].blank? && @user.update_without_password(user_params)) || @user.update(user_params)
        sign_in(@user, bypass: true)
        redirect_to '/dashboard', notice: 'Your profile changes have been saved.'
      else
        render 'edit'
      end
    end
    

    There's also of course a view script (a haml one in my case - app/views/profiles/edit.html.haml) that uses form_for to render the form:

    = form_for current_user, as: :user, url: profile_path, html: { class: '' } do |f|
    # [f.label, f.text_field, f.password_field, etc...]
    

    My user model also has all the devise-y goodness included:

    class User < ActiveRecord::Base
      # Include default devise modules. Others available are:
      # :confirmable, :lockable, :timeoutable and :omniauthable
      devise :database_authenticatable, :registerable,
             :recoverable, :rememberable, :trackable, :validatable
      has_many :receipts
      validate :first_name, presence: true
      validate :last_name, presence: true
      validate :email, presence: true, email: true
    end