「フット・イン・ザ・ドア」の心理を考えると、私は会員登録の時、最初にメールアドレスだけでメールを確認し、メールのリンクを踏んだ本登録時にパスワードやその他の情報を登録させた方が会員登録の確率が上がると思っています。
そこでrailsのdeviseで最初の入力をメールアドレスの入力だけで済まし、本登録時にパスワードを登録するようにカスタマイズしてみました。
バージョンは下記です。
rails (5.1.4)
devise (4.3.0)
下記のページが参考になりました。
https://github.com/plataformatec/devise/wiki/How-To:-Email-only-sign-up
rails初アプリなのでお作法とか色々まずいところあると思いますのでご指摘いただければと思います。
コントローラーとViewを生成する
コントローラーやビューをカスタマイズできるよう作成します。
rails g devise:views
rails g devise:controllers users
コントローラーは今回userモデルなのでusers配下に作ります。詳しくは後述しますが、コントローラーはルートを書かないと作っただけではデフォルトのコントローラが採用されカスタマイズできないので注意してください。
新規登録フォームからパスワードを削除
パスワードとパスワード確認の
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true %>
</div>
- <div class="field">
- <%= f.label :password %>
- <% if @minimum_password_length %>
- <em>(<%= @minimum_password_length %> characters minimum)</em>
- <% end %><br />
- <%= f.password_field :password, autocomplete: "off" %>
- </div>
-
- <div class="field">
- <%= f.label :password_confirmation %><br />
- <%= f.password_field :password_confirmation, autocomplete: "off" %>
- </div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
新規登録時にパスワードが空でも送信できるようにする
ただ、ビューからフォームを消しただけではバリデータに引っかかって送信できないので、それをどうにかします。
class User < ApplicationRecord
...
def password_required?
super && confirmed?
end
end
新規登録時にログインしてしまうのを防止
デフォルトの挙動はサインアップ時にログインしてしまいますが、今回の流れだとまだログインするのはおかしいのでそれを阻止します。
class User < ApplicationRecord
...
def active_for_authentication?
super && confirmed?
end
def inactive_message
confirmed? ? super : :needs_confirmation
end
end
active_for_authentication?
はユーザーの状態によって、ログインを阻止するメソッドです。inactive_message
はその際のフラッシュメッセージのキーを返します。キーは頭にsigned_up_but_
が付与されI18n
のキーに使用されます。
フラッシュメッセージが正しく表示されるように文言を登録します。
ja:
devise:
registrations:
signed_up_but_needs_confirmation: "ご登録いただいたメールアドレスにメールを送信しました。メールを確認してアカウントを有効化してください。"
これで、確認用のメールアドレスが送信されるまでは完了です。
パスワード登録画面(本登録画面)
メールに記載してある本登録画面がhttp://localhost:3000/users/confirmation?confirmation_token=***
のようなURLになっています。rails routes
で確認するとこれはusers/confirmations#show
に割り当ててありますのでそれを実装します。
コントローラーをカスタマイズする場合、対象のコントローラーのルートを指定する必要があります。
Rails.application.routes.draw do
devise_for :users, controllers: {
:confirmations => 'users/confirmations',
}
showアクションを実装します。
class Users::ConfirmationsController < Devise::ConfirmationsController
...
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
super if resource.nil? or resource.confirmed?
end
confirmation_token
からuserのモデルを取得してメンバー変数にアサインしてます。
ビューはこんな感じ。
<h2><%= "パスワードの登録" %></h2>
<%= form_for(resource, as: resource_name, url: users_confirm_path) do |f| %>
<%= devise_error_messages! %>
<div class="field">
<%= f.label :password %>
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "off" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "off" %>
</div>
<%= f.hidden_field :confirmation_token %>
<div class="actions">
<%= f.submit "登録" %>
</div>
<% end %>
フォームのサブミット先のusers_confirm_path
はまだ存在しません。これから作ります。まずはルートから作ります。
Rails.application.routes.draw do
devise_for :users, controllers: {
:confirmations => 'users/confirmations',
}
devise_scope :user do
patch "users/confirm" => "users/confirmations#confirm"
end
class Users::ConfirmationsController < Devise::ConfirmationsController
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token]) if params[:confirmation_token].present?
super if resource.nil? or resource.confirmed?
end
def confirm
confirmation_token = params[resource_name][:confirmation_token]
self.resource = resource_class.find_by_confirmation_token!(confirmation_token)
if resource.update_attributes(confirm_params)
self.resource = resource_class.confirm_by_token(confirmation_token)
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, resource)
else
render :action => "show"
end
end
private
def confirm_params
params.require(resource_name).permit(:password, :password_confirmation)
end
end
送信されたパスワードとconfirmed_at
を現在時刻に更新しています。confirmed_at
がnilのままだとconfirmed?
がfalseを返すので更新する必要があります。
以上です。最後にテストを書いたので載せておきます。
require 'test_helper'
class SignUpTest < ActionDispatch::IntegrationTest
test "入会時はメールアドレスだけで確認でき、有効化画面でパスワードで登録できます。" do
get new_user_registration_path
assert_template 'devise/registrations/new'
assert_no_difference 'User.count' do
post user_registration_path, params: { user: { email: "" } }
end
assert_template 'devise/registrations/new'
assert_difference 'User.count', 1 do
post user_registration_path, params: { user: { email: "sign_up_test@example.com" } }
end
follow_redirect!
assert_template 'root_pages/index'
user = User.find_by_email("sign_up_test@example.com")
assert_not user.confirmed?
assert_nil controller.current_user
get user_confirmation_path(confirmation_token: user.confirmation_token)
assert_template 'devise/confirmations/show'
patch users_confirm_path, params: {user: {
password: "",
password_confirmation: "",
confirmation_token: user.confirmation_token
}}
assert_not user.reload.confirmed?
assert_template 'devise/confirmations/show'
patch users_confirm_path, params: {user: {
password: "password",
password_confirmation: "password2",
confirmation_token: user.confirmation_token
}}
assert_not user.reload.confirmed?
assert_template 'devise/confirmations/show'
patch users_confirm_path, params: {user: {
password: "password",
password_confirmation: "password",
confirmation_token: user.confirmation_token
}}
follow_redirect!
assert user.reload.confirmed?
assert_template 'root_pages/index'
assert_equal controller.current_user.email, "sign_up_test@example.com"
end
end