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
参考