よくある下記のようなアカウント編集ページをdeviseで作る場合。

https://gyazo.com/e7b9fc1bbc0df4ccc501d3d861e76e58

  • パスワードとパスワード(確認)を入力した場合はパスワードを更新。
  • パスワードを入力しなくても他の項目(emailなど)は更新可能。

こんな感じでresource.update_without_passwordを使い分ければOK。

# app/controllers/registrations_controller.rb:
class RegistrationsController < Devise::RegistrationsController
  protected
    def update_resource(resource, params)
      if params[:password].present? && params[:password_confirmation].present?
        resource.update_attributes(params)
      else
        resource.update_without_password(params)
      end
    end
end

簡単だけどdeviseのWikiには載ってないようなので。

deviseのconfirmableモジュールを使うとデフォルトでemailを変更するときに再確認メールを送信する。

(これがサインアップ時のメールと同じ内容なので使い辛いというのは別の話)

設定でオフにできる。

config/initializers/devise.rb:

Devise.setup do |config|
  config.reconfirmable = false
end

サインアウト機能のテストでよくある。visitメソッドだとgetしか飛ばせない。

実際にサインアウトリンクをクリックするのはサインアウトのテストだったらいいけど、他のテストの後処理として使う場合はすごく遅くなりそうで嫌だ

deviseには下記のような設定ができるらしいけど、これテストしてることになんの?って気がするので却下。

config.sign_out_via = Rails.env.test? ? :get : :delete

これで行けた。

page.driver.submit :delete, '/users/sign_out', {}
# config/routes.rb:
devise_for :users,
  controllers: {
    registrations: :registrations,
    omniauth_callbacks: 'users/identities'
  } do
    delete 'users/disconnect/:provider' => 'users/identities#disconnect_omniauth_provider', as: 'disconnect_omniauth_provider'
end
$ rake spec
(...)
DEPRECATION WARNING: Passing a block to devise_for is deprecated. Please remove the block from devise_for (only the block, the call to devise_for must still exist) and call devise_scope :user do ... end with the block instead. (called fr)

上記のようにdevise_forにblockを渡すスタイルはDEPRECATEDになった。

# config/routes.rb:
devise_for :users, controllers: {
  registrations:      :registrations,
  omniauth_callbacks: 'users/identities'
}

devise_scope :user do
  delete 'users/disconnect/:provider' => 'users/identities#disconnect_omniauth_provider',
    as: 'disconnect_omniauth_provider'
end

こんな感じでblockに渡してた部分はdevise_scopeを使うといいらしい。

怖話Andyというユーザー名で登録しようとするとエラー。

Mysql2::Error - Duplicate entry 'Andy' for key 'index_users_on_name'

多分小文字のandyとぶつかってるんだろうな。

validates :name, uniqueness: trueしてるのに何故だろう。mysqlのcollationが変なのになってるのかなと確認してみるも問題無し。

uniquenessを確認している部分のSQLクエリをみてみると・・・

SELECT 1 AS one FROM `users` WHERE `users`.`name` = BINARY 'Andy' LIMIT 1

BINARY・・・だと・・・!?

deviseの設定でcase_insensitive_keysを設定しないとBINARYで確認しにいくようです。

# config/initializers/devise.rb:
config.case_insensitive_keys = [ :email, :name ]

ここにnameも追加してOK。configのコメントぐらいちゃんと読んどけってことですな。

deviseはデフォルトでuserの更新(Devise::RegistrationsController#update)に現在のパスワード(current_password)が要る。

ソースを見てみるとmodelにupdate_without_passwordというのがあるのでこれかと思いきや、これはpasswordとpassword_confirmation無しでupdateするものだった。

自分でupdate_without_current_passwordを作る。

# app/models/user.rb:
class User < ActiveRecord::Base
  def update_without_current_password(params, *options)
    params.delete(:current_password)                                                                                  

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

    clean_up_passwords
    update_attributes(params, *options)
  end
end

controllerからもこれを使うようにする。

# app/controllers/registrations_controller.rb:
class RegistrationsController < Devise::RegistrationsController    
  def update
    @user = User.find(current_user.id)
    if @user.update_without_current_password(params[:user])
      sign_in @user, bypass: true
      set_flash_message :notice, :updated
      redirect_to after_update_path_for(@user)
    else
      render 'edit'
    end
  end
end

面倒ですね。