Edited at

deviseで、パスワードを登録していないユーザーはパスワード追加、登録しているユーザーはパスワードを更新する

More than 3 years have passed since last update.

OmniAuthなどを使っていて、


  • OmniAuthで外部サービスと連携している場合はパスワード不要


    • パスワード不要でも、パスワード追加、更新は可能



  • OmniAuthで外部サービスと連携していない場合はパスワード必須

のような状況で、Deviseでのパスワード変更機能を作った。

Deviseでログイン機能を作ると、edit_user_registration_pathが自動に作られるはずなので、そこをカスタマイズする。

link_to('パスワードを変える', edit_user_registration_path(@user))

パスワード変更の入力フォームは以下のように書く。


registrations.html.slim

.main

.container
.row
.span4.offset4
h2
| パスワード変更
= resource_name.to_s.humanize
= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f|
= devise_error_messages!
- if current_user.encrypted_password.present?
div
= f.label :current_password, '現在のパスワード'
br
= f.password_field :current_password
div
= f.label :password, '新しいパスワード'
br
= f.password_field :password, :autocomplete => 'on'
div
= f.label :password_confirmation, '新しいパスワードをもう一度'
br
= f.password_field :password_confirmation
div
= f.submit '更新する'

User情報を更新するコントローラメソッドはDevise::RegistrationsControllerのupdate。


registration_controller.rb

 # PUT /resource

# We need to use a copy of the resource because we don't want to change
# the current user in place.
def update
self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)

if update_resource(resource, account_update_params)
yield resource if block_given?
if is_flashing_format?
flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
:update_needs_confirmation : :updated
set_flash_message :notice, flash_key
end
sign_in resource_name, resource, bypass: true
respond_with resource, location: after_update_path_for(resource)
else
clean_up_passwords resource
respond_with resource
end
end

# ...

protected

# ...

# By default we want to require a password checks on update.
# You can overwrite this method in your own RegistrationsController.
def update_resource(resource, params)
resource.update_with_password(params)
end


その中でも、実際に更新を行っているのは if update_resource(resource, account_update_params) の部分。

なので、deviseを使っているモデルでupdate_resourceをoverrideして、もしもまだパスワードを登録していないときは、パスワードを確認せずにアップデートして、パスワードを登録しているときはパスワードを確認してアップデートする。


registrations_controller.rb


class RegistrationsController < Devise::RegistrationsController
before_filter :configure_permitted_parameters, if: :devise_controller?

#override
def update_resource(resource, params)
resource.update_with_password_if_password_present(params)
end

def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :pen_name, :password, :password_confirmation, :current_password) }
end


このように、DeviseのRegistrationsControllerを継承した先でoverride。


user.rb

class User < ActiveRecord::Base

# 省略

devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable

# 省略

def update_with_password_if_password_present(params)
if encrypted_password.present?
return update_with_password(params)
else
result = update_attributes(params)
clean_up_passwords
return result
end
end


なお、DatabaseAuthenticatableのupdate_with_passwordの実装は以下のようなもの。


database_authenticatable.rb

      # Update record attributes when :current_password matches, otherwise returns

# error on :current_password. It also automatically rejects :password and
# :password_confirmation if they are blank.
def update_with_password(params, *options)
current_password = params.delete(:current_password)

if params[:password].blank?
params.delete(:password)
params.delete(:password_confirmation) if params[:password_confirmation].blank?
end

result = if valid_password?(current_password)
update_attributes(params, *options)
else
self.assign_attributes(params, *options)
self.valid?
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
end

clean_up_passwords
result
end


参考