LoginSignup
20
18

More than 5 years have passed since last update.

diveseのconfirmationでメールアドレスのみで仮登録を実装

Last updated at Posted at 2017-10-20

「フット・イン・ザ・ドア」の心理を考えると、私は会員登録の時、最初にメールアドレスだけでメールを確認し、メールのリンクを踏んだ本登録時にパスワードやその他の情報を登録させた方が会員登録の確率が上がると思っています。

そこで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配下に作ります。詳しくは後述しますが、コントローラーはルートを書かないと作っただけではデフォルトのコントローラが採用されカスタマイズできないので注意してください。

新規登録フォームからパスワードを削除

パスワードとパスワード確認の

app/views/divise/registrations/new.html.erb
<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" %>

新規登録時にパスワードが空でも送信できるようにする

ただ、ビューからフォームを消しただけではバリデータに引っかかって送信できないので、それをどうにかします。

app/models/user.rb
class User < ApplicationRecord
  ...

  def password_required?
    super && confirmed?
  end
end

新規登録時にログインしてしまうのを防止

デフォルトの挙動はサインアップ時にログインしてしまいますが、今回の流れだとまだログインするのはおかしいのでそれを阻止します。

app/models/user.rb
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のキーに使用されます。

フラッシュメッセージが正しく表示されるように文言を登録します。

config/locales/devise.ja.yml
ja:
  devise:
    registrations:
      signed_up_but_needs_confirmation: "ご登録いただいたメールアドレスにメールを送信しました。メールを確認してアカウントを有効化してください。"

これで、確認用のメールアドレスが送信されるまでは完了です。

パスワード登録画面(本登録画面)

メールに記載してある本登録画面がhttp://localhost:3000/users/confirmation?confirmation_token=***のようなURLになっています。rails routesで確認するとこれはusers/confirmations#showに割り当ててありますのでそれを実装します。

コントローラーをカスタマイズする場合、対象のコントローラーのルートを指定する必要があります。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    :confirmations => 'users/confirmations',
  }

showアクションを実装します。

app/controllers/users/confirmations_controller.rb
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のモデルを取得してメンバー変数にアサインしてます。

ビューはこんな感じ。

app/views/devise/confirmations/show.html.erb
<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はまだ存在しません。これから作ります。まずはルートから作ります。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    :confirmations => 'users/confirmations',
  }
  devise_scope :user do
    patch "users/confirm" => "users/confirmations#confirm"
  end
app/controllers/users/confirmations_controller.rb
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を返すので更新する必要があります。

以上です。最後にテストを書いたので載せておきます。

test/integration/sign_up_test.rb
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
20
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
18