2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[devise カスタマイズ]ユーザー登録完了前に(仮登録の状態で)他のカラムを追加させる

Last updated at Posted at 2020-08-10

[devise]ユーザー登録完了前に(仮登録の状態で)他のカラムを追加させる

はじめに

ruby on railsのユーザー周り(会員登録など)のgemといえばdeviseですかね・・?
一瞬で会員登録とかができるのは物凄い便利です。

そんな便利な反面、カスタマイズするのにはかなり時間がかかります。
(今回もかなり時間がかかりました。)

今回実装するのは、以下のような機能です。

# 実装したい内容
会員登録(emailpassword

認証メール送信

メールのリンクからプロフィール(今回はname)を追加するページへ

プロフィールを入力して確認画面へ

確認画面で送信

会員登録完了(ログイン画面へ)

イメージ
hoge.gif

今回のissueは大きく二つ

  • 会員登録を仮登録の状態で編集する
  • 変更時に編集内容を確認するページを挟む

開発環境

- rails (6.0.3.2)
- ruby 2.6.5p114
- devise (4.7.2)
- letter_opener (1.7.0)

- mac Catalina 10.15.6

セットアップ

前提として、以下の状態を想定しています

  • deviseがインストールされている
  • deviseのview,controller,modelが作成されている
  • Useモデルが存在する(カラムはデフォルトにnameを追加)
  • letter openerをgemにインストールしている(初期設定している)
  • メール認証をtrueに変更している

使い方

今回のissueは大きく二つ

  • 会員登録を仮登録の状態で編集する => (解決策) confirmation_tokenを保持して、最後にconfirmation_pathに引数として与える
  • 変更時に編集内容を確認するページを挟む => (解決策)hidden_fieldを入れることでデータを保持する

1つ目のissueの肝は、[confirmation_token]
仮登録完了時に送られるメールをみるとわかりやすい

/app/views/devise/mailer/confirmation_instructions.html.erb
<p>Welcome <%= @email %>!</p>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>

なんか、認証トークンとuserのデータを引数に持ってconfirmation_urlに入ったら仮登録→本登録完了になるらしい
つまり、
__これをやらずに__プロフィールを編集して、その後認証トークンを与えてconfirmation_tokenにアクセスして完了すればいい。






それでは実際にやってみる

① ルーティングを設定

まず、追加するアクションは三つ。そのルーティングを行う。

今回はこの三つ。カッコの中身がアクション名(センスなくてすみません・・・)
[入力、確認、更新]

  • 入力:プロフィール入力アクション(before_create)
  • 確認:入力内容確認アクション(before_confirm)
  • 更新:入力内容で更新するアクション(before_update)
config/routes.rb
Rails.application.routes.draw do

  devise_for :users, :controllers => {
    # コントローラーを見に行くようになる
    # 無しだと、そもそも見に行かずにデフォルトが実行される
    # (結論:deviseのコントローラーを変更したら記述が必須)
    :registrations => 'users/registrations',
    :sessions => 'users/sessions',
    :confirmations => 'users/confirmations'
  }

  devise_scope :user do
  # ルーティングを指定
  # アクションが追加されたらそのルーティングを指定する

    get "sign_in", :to => "users/sessions#new"
    get "sign_out", :to => "users/sessions#destroy"

    #プロフィール編集画面(仮登録状態)
    get "before_sign_up", :to => "users/registrations#before_create"
    #プロフィール編集内容確認画面(仮登録状態)
    post "before_sign_up_confirm", :to => "users/registrations#before_confirm"
    #プロフィール編集内容のアップデート処理(仮登録→本登録に)
    post "before_sign_up", :to => "users/registrations#before_update"

  end

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  root 'pages#index'
  get 'pages/show'
end

大きくやったことは二つ

  • device_forでdeviseのcotrollerをカスタマイズ可能に
  • devise_scoopeで新しいアクションのパスを指定

② プロフィール編集アクションを追加(入力、確認、更新の三つ)

続いて、アクションの追加

この三つ。カッコの中身がアクション名

  • 入力:プロフィール入力アクション(before_create)
  • 確認:入力内容確認アクション(before_confirm)
  • 更新:入力内容で更新するアクション(before_update)

とりあえず、binding.pryで引数を見て回ったのと、superが表す内容をそれぞれ見て回った(こんなサイトで)


まずは ### **入力:プロフィール入力アクション(before_create)**
registrations_controller.rb
  # 仮登録状態のプロフィール入力画面(メール認証後のアクション)
  def before_create
    # 引数のresourceを使ってユーザーを取得
    @user =  User.find(params["resource"])
    @token = params["confirmation_token"]
  end
before_create.html.erb
<h2>プロフィールを登録</h2>
<%= form_for(resource, as: resource_name, url: before_sign_up_confirm_path(resource_name, confirmation_token: @token) ,html: {method: "post"}) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>
  <%= f.hidden_field :user_id, :value =>  resource.id %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true, autocomplete: "name" %>
  </div>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email",:readonly => true %>
  </div>
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

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

デフォルトだったらparamsとかじゃなくてresourceで一発で呼べるんですけど、新しく追加したメソッドでresourceではとれない

余談
(厳密には、以下のような感じで取れるようにできるんですが、ログイン必須なので、仮登録では面倒そうだったので断念)
registrations_controller.rb
**prepend_before_action :authenticate_scope! , only: [:before_create]**を追加

(余談の出来事)ということもあり、地道に実装することに。

1- 実は仮登録で、newされてるらしいので、newせずに@userにとってくる
2- confirmation_tokenは持ち続けたいので一旦@token
3- viewのhidden_fieldでuserのidを保持(これしなくても、paramsから取れる)

確認:入力内容確認アクション(before_confirm)

registrations_controller.rb
  # 仮登録状態のプロフィール入力完了画面(プロフィール入力後のアクション)
  def before_confirm
    @user =  User.find(params["user"]["user_id"])
    @user.name = params["user"]["name"]
    @token = params["confirmation_token"]
    if @user.valid?
      render :action => 'before_confirm'
      flash.now[:success] = '確認して完了してください'
    else
     render :action => 'before_create'
     flash.now[:alert] = '失敗しました'
    end
  end
before_confirm.html.erb
<h2>確認画面</h2>
<%= form_for(@user, url: before_sign_up_path(@user, confirmation_token: @token),html: {method: "post"}) do |f| %>
  <%= render "devise/shared/error_messages", resource: @user %>
  <%= f.hidden_field :user_id, :value =>  @user.id %>
  <%= f.hidden_field :email, :value =>  @user.email %>
  <%= f.hidden_field :name, :value =>  @user.name %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true, autocomplete: "name",:readonly => true %>
  </div>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email",:readonly => true %>
  </div>
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

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

ここでsaveしないんですね。
@userに入れてみて、validかinvalidかを調べてるだけです。次のupdateで再び呼び出してからsaveします(ここがかなり効率悪いですよね・・・)

更新:入力内容で更新するアクション(before_update)

registrations_controller.rb
  # 仮登録状態のプロフィール入力内容更新処理
  def before_update
    @user =  User.find(params["user"]["user_id"])
    @token = params["confirmation_token"]
    @user.save
    if @user.valid?
      # ここが肝!
      #  confirmation_pathにuserデータと認証トークンを付与することで本会員登録される
      redirect_to confirmation_path(@user, confirmation_token: @token)
      flash[:success] = '確認して完了してください'
    else
      render :action => 'before_create'
      flash.now[:alert] = '失敗しました'
    end
  end

③ mailのパスを変更

最後に、
送られてくるメールの遷移先をを変更します。
confirmation_url → before_sign_up_url
resourceもparamsで呼び出せるように追加 @resource → resource:@resource
(この辺深く理解してないのでミス多いかもです。すみません。)

app/views/devise/mailer/confirmation_instructions.html.erb
<p>Welcome <%= @email %>!</p>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', before_sign_up_url(resource:@resource, confirmation_token: @token) %></p>

デモ

ソースコード(github)

終わりに

gemってデフォルトで使う分にはすごい便利だけど、カスタマイズするにはgem自体をすごい理解しないといけないですよね。
特に初学者にはキツすぎる・・・

勉強不足で間違いなどあるかもしれませんが、その時は優しく指摘していただきたいです。
よろしくお願いします!

参考サイト

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?