deviseでユーザー名とパスワードでの認証がうまくいかなかった件

まずは今朝の最後に貼った画面を再掲する。

sign-in-with-name-and-email

見ての通り、サインアップするのにユーザー名、メールアドレス、パスワードの3つが必要だ。これでは面倒なので、ユーザー名とパスワードだけでサインインできるようにしたいと思った。

で、グーグルさんでいろいろ調べてみるとわりと簡単そうだったんだけど、結果から言うとうかくいかなかった。なんでだかわからない。
config/initializers/devise.rb の編集もしたし、ApplicationController に before_filter (configure_permitted_parameters)も書いてみた。けどうまくいかなかった。なんか色々やってるうちにわからなくなってしまった。
さらに、サインアップのときに登録したはずのユーザー名がデータベースに保存されていないことが発覚。ということはつまり、今朝の時点でもうまくいっていなかったってことだ。

結局、メールアドレスとパスワードで認証するところまで巻き戻して、devise をこれ以上いじるのはやめにして、他を進めることにした。devise は、また何か新しい情報を見つけたら試してみよう。

deviseをカスタマイズする

Rails と devise の話はまだ続くよ。今日は devise のビューとコントローラをカスタマイズする。

やっぱりユーザー名がほしい

ユーザーアカウントに Email だけじゃなくて、やっぱりユーザー名がほしい。というわけで、migration スクリプトに2行追加。

class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table(:users) do |t|
      ## Database authenticatable
      t.string :name, null: false, default: ""
      t.string :email, null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string :current_sign_in_ip
      t.string :last_sign_in_ip

      ## Confirmable
      t.string :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string :unconfirmed_email      # Only if using reconfirmable

      ## Lockable
      # t.integer :failed_attempts, default: 0, null: false
      # Only if lock strategy is :failed_attempts
      # t.string :unlock_token
      # Only if unlock strategy is :email or :both
      # t.datetime :locked_at t.timestamps
    end

    add_index :users, :name, unique: true
    add_index :users, :email, unique: true
    add_index :users, :reset_password_token, unique: true
    add_index :users, :confirmation_token, unique: true
    # add_index :users, :unlock_token, unique: true
  end
end

そしてデータベースの作り直し。

takatoh@nightschool:~/w/lathercraft$ bundle exec rake db:drop
takatoh@nightschool:~/w/lathercraft$ bundle exec rake db:migrate

devise のビューをコピーしてカスタマイズ

デフォルトでは gem のビューが使われているけど、カスタマイズするためにビューをコピーする。

takatoh@nightschool:~/w/lathercraft$ bundle exec rails generate devise:views users
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/users/shared
      create    app/views/users/shared/_links.erb
      invoke  form_for
      create    app/views/users/confirmations
      create    app/views/users/confirmations/new.html.erb
      create    app/views/users/passwords
      create    app/views/users/passwords/edit.html.erb
      create    app/views/users/passwords/new.html.erb
      create    app/views/users/registrations
      create    app/views/users/registrations/edit.html.erb
      create    app/views/users/registrations/new.html.erb
      create    app/views/users/sessions
      create    app/views/users/sessions/new.html.erb
      create    app/views/users/unlocks
      create    app/views/users/unlocks/new.html.erb
      invoke  erb
      create    app/views/users/mailer
      create    app/views/users/mailer/confirmation_instructions.html.erb
      create    app/views/users/mailer/reset_password_instructions.html.erb
      create    app/views/users/mailer/unlock_instructions.html.erb

とりあえず、sign up するときの app/views/users/registrations.new.html.erb と sign in するときの app/views/users/sessions/new.html.erb にユーザー名の欄を付け加えた。

<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>
  <div>
    <%= f.label :name %>
    <%= f.email_field :name, autofocus: true %>
  </div>
  <div>
    <%= f.label :email %>
    <%= f.email_field :email %>
  </div>
  <div>
    <%= f.label :password %>
    <%= f.password_field :password, autocomplete: "off" %>
  </div>
  <div>
    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation, autocomplete: "off" %>
  </div>
  <div>
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "users/shared/links" %>
<h2>Sign in</h2>
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  <div>
    <%= f.label :name %>
    <%= f.email_field :name, autofocus: true %>
  </div>
  <div>
    <%= f.label :email %>
    <%= f.email_field :email %>
  </div>
  <div>
    <%= f.label :password %>
    <%= f.password_field :password, autocomplete: "off" %>
  </div>
  <% if devise_mapping.rememberable? -%>
  <div>
    <%= f.check_box :remember_me %>
    <%= f.label :remember_me %>
  </div>
  <% end -%>
  <div>
    <%= f.submit "Sign in" %>
  </div>
<% end %>

<%= render "users/shared/links" %>

コントローラーのカスタマイズ

コントローラーをカスタマイズするには、Devise::SessionsController, Devise::RegistrationsController, Devise::PasswordsController を継承したクラスを作成して、各メソッドをオーバーライドすればいいようだ。どのメソッドをオーバーライドすればいいかを、bundle exec rake routes で確認する。

takatoh@nightschool:~/w/lathercraft$ bundle exec rake routes
                  Prefix Verb   URI Pattern                       Controller#Action
        new_user_session GET    /users/sign_in(.:format)          devise/sessions#new
            user_session POST   /users/sign_in(.:format)          devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)         devise/sessions#destroy
           user_password POST   /users/password(.:format)         devise/passwords#create
       new_user_password GET    /users/password/new(.:format)     devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format)    devise/passwords#edit
                         PATCH  /users/password(.:format)         devise/passwords#update
                         PUT    /users/password(.:format)         devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)           devise/registrations#cancel
       user_registration POST   /users(.:format)                  devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)          devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)             devise/registrations#edit
                         PATCH  /users(.:format)                  devise/registrations#update
                         PUT    /users(.:format)                  devise/registrations#update
                         DELETE /users(.:format)                  devise/registrations#destroy
       user_confirmation POST   /users/confirmation(.:format)     devise/confirmations#create
   new_user_confirmation GET    /users/confirmation/new(.:format) devise/confirmations#new
                         GET    /users/confirmation(.:format)     devise/confirmations#show
(後略)

これを見ると、とりあえずは registration#new と sessions#new かな。それぞれこうした。

class Users::RegistrationsController < Devise::RegistrationsController
end
Devise::SessionsController
  def new
    super
  end

  def create
    super
  end

  def destroy
    super
  end
end

ついでに PasswordsController も。

class Users::PasswordsController < Devise::PasswordsController
end

ルーティングの設定

作ったコントローラーを使うように、ルーティングを変更する。

Rails.application.routes.draw do
  devise_for :users, :controllers => {
    :registrations => "users/registrations",
    :sessions      => "users/sessions",
    :passwords     => "users/passwords"
  }
(後略)

ルーティングを確認してみよう。

takatoh@nightschool:~/w/lathercraft$ bundle exec rake routes
                  Prefix Verb   URI Pattern                       Controller#Action
        new_user_session GET    /users/sign_in(.:format)          users/sessions#new
            user_session POST   /users/sign_in(.:format)          users/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)         users/sessions#destroy
           user_password POST   /users/password(.:format)         users/passwords#create
       new_user_password GET    /users/password/new(.:format)     users/passwords#new
      edit_user_password GET    /users/password/edit(.:format)    users/passwords#edit
                         PATCH  /users/password(.:format)         users/passwords#update
                         PUT    /users/password(.:format)         users/passwords#update
cancel_user_registration GET    /users/cancel(.:format)           users/registrations#cancel
       user_registration POST   /users(.:format)                  users/registrations#create
   new_user_registration GET    /users/sign_up(.:format)          users/registrations#new
  edit_user_registration GET    /users/edit(.:format)             users/registrations#edit
                         PATCH  /users(.:format)                  users/registrations#update
                         PUT    /users(.:format)                  users/registrations#update
                         DELETE /users(.:format)                  users/registrations#destroy
       user_confirmation POST   /users/confirmation(.:format)     devise/confirmations#create
   new_user_confirmation GET    /users/confirmation/new(.:format) devise/confirmations#new
                         GET    /users/confirmation(.:format)     devise/confirmations#show
(後略)

confirmation のルーティングが devise のままだけど大丈夫かな。

試しにサインアップしてみると、無事うまく行った。

sign-in-with-name-and-email