Devise でメールアドレスではなくユーザー名とパスワードでサインインできるようにする方法メモ

Devise ではメールアドレスとパスワードを使ってサインインするのがデフォルト。

メールアドレスではなくユーザー名、それとパスワードでサインインしたい場合はどうすればいいか? Devise が呼び出すモデルとコントローラーのメソッドを上書きするしかない。

やったことをメモしておく。

ユーザー名を保存する列を追加

すでに users テーブルを作っていたので、users テーブルに列を追加する。

$ rails generate migration AddUsernameToUsers

マイグレーションファイルの中身は次の通り。

class AddUsernameToUsers < ActiveRecord::Migration
  def change
    add_column :users, :username, :string, null: false, default: ""

    add_index :users, :username, unique: true
  end
end

あとはマイグレーションを実行。

認証のキーにユーザー名を使えるようにモデルを修正

ユーザー名を使ってサインインするのは User だけなので、 User クラスで認証に使うキーを指定する。

class User < ActiveRecord::Base
  # 認証で使うキーを指定
  devise :database_authenticatable, :trackable, :validatable,
    authentication_keys: [:username]

  # ユーザー名で検索
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if username = conditions.delete(:username)
      where(conditions).where(username: username).first
    else
      where(conditions).first
    end
  end

  # 登録時に email を不要にする
  def email_required?
    false
  end
  def email_changed?
    false
  end
end

ユーザー名を認証時のパラメータとして使えるようにコントローラーを修正

ユーザー名が Strong Parameters ではじかれないように対策が必要。

class ApplicationController < ActionController::Base
  before_filter :configure_permitted_parameters, if: :devise_controller?

  # ...省略...

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:subdomain, :username, :email, :password, :password_confirmation) }
    devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:subdomain, :username, :email, :password, :remember_me) }
  end
end

ユーザー名を入力できるようにカスタムビューを作成

ユーザー名とパスワードでサインインするのは User だけなので、 異なるビューを使えるように initializers/devise.rb で設定する。

Devise.setup do |config|
  # ...省略...

  config.scoped_views = true
end

ビューを生成。

$ rails generate devise:view users

app/views/users/sessions/new.html.erb を修正し、ユーザー名とパスワードを入力できるようにする。

<%= form_for(resource,
             as: resource_name,
             url: session_path(resource_name),
             html: { class: "form-signin", role: "form" }) do |f| %>
  <h2 class="form-signin-heading">
    サインイン
  </h2>

  <%= f.text_field :username,
    class: "form-control",
    placeholder: "ユーザー名" %>

  <%= f.password_field :password,
    autocomplete: "off",
    class: "form-control",
    placeholder: "パスワード" %>

  <%= f.submit "サインイン",
    data: { disable_with: "サインイン中..." },
    class: "btn btn-lg btn-primary btn-block" %>
<% end %>

なお、今回作っているアプリは「ユーザー登録を行うのは管理者ユーザーのみ」という仕様だったので、 ユーザー名とパスワードでのサインアップは省いた。