deviseのwikiにあるHow To: Automatically generate password for users (simpler registration)をやってみる。
通常、deviseを使った会員登録ではemailとpasswordの入力が必須になりますが、「会員登録時にパスワードを設定しなくてはならない」という仕様はユーザーの負担になることもあります。 例えば「できるだけ会員登録を簡単にしたい」という要件があるケースでは、パスワードの入力を省略させたいこともあると思います。
そのような場合は、一旦システム側で自動でパスワードを設定してしまうのも手でしょう。 今回はdeviseの機能を利用して、会員登録時にパスワードを自動設定する方法を紹介します。
参考: wiki
言語、ライブラリのバージョン
導入から基本的な実装を知りたい場合は最小構成導入からログアウトまでを参照。 先に結論を知りたい方はこちら Userモデルの構造はこんな感じ
create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "name", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end
deviseでUserモデルを作成した場合のデフォルト構成にnameカラムを追加しています。
読んでみる
冒頭から翻訳してみる。(意訳有り)
It's now increasingly common for websites to provide "super fast & easy" registration : the user just gives his e-mail. The confirmation e-mail then contains a password generated for the user. If the user is not happy with the password (either wants a super-weak or super-strong one), the ability to change it is given right away when accessing the confirmation link.
最近のWebサイトではとっても早くて簡単な登録ができることがますます一般的になっている。ユーザーは自分のメールアドレスを提供するだけで良い。確認メールには生成されたパスワードが記載され、もしユーザーがそのパスワードに不満があれば(脆弱すぎたり強すぎたりした場合)、すぐに確認リンクを踏んでパスワードの変更ができるようになる。
記事にあったコードは以下です。
generated_password = Devise .friendly_token.first(8) user = User .create!(:email => email、:password => generated_password) RegistrationMailer .welcome(user、generated_password).deliver
続いて、上記のコードに関してQA形式で補足があるので、それも訳してみましょう。
Question: - ここではdevise RegistrationControllerのcreateアクションをオーバーライドしていますか? - これを実装したコントローラーのソースをみることはできますか? - 新しく作成されたパスワードを記載したwelcomメッセージの詳細を教えてほしい - パスワードを変更したい場合どうすればいいの?
Answer: 私からの回答はそれほどシンプルでも無いし、簡単でもありません。私は devise RegistrationControllerを上書きしていないですし、全く使用していません。ユーザーがアカウントをシステムに直接作成する手段を提供していません。ワークフローを通過し、その最後に上記のコードを使用してアカウントを作成、電子メールを送信しています。上記のコードに加えてこのコードを使用しています。
sign_in(:user, user)
これによってシームレスに新しいユーザーとしてログインすることが可能です。ログイン機能は、しばらくしてから再度ログインしたいときに利用されます。性質上、パスワードや確認手続き(メールの存在確認)は必要ありません。
お、おう、全然参考にならんぞ。。まあ質問に対しての部分的な回答にはなっています。この人のサイトでは一連のワークフローの最後にメールアドレスを渡して、自動で登録処理、ログインが行われる実装がなされているようです。そして性質上何度もログインするようなサービスでは無いっぽい。。
このままだとあまり得るものがなさそうなので上記のコードを使って新規登録にパスワードを生成するようにしていきたいと思います。
View側の変更を行う
今回やりたいことはパスワードを入れずに新規登録したいということなので、既存の新規登録画面にあるpasswordfieldは不要です。まずはいらないものを消してしまいましょう。
app/views/devise/registrations/new.html.slim
にある記述から、passwordに関するinputを削除します。
元の内容はこちら。デフォルトの内容にnameを入力するフィールドを足した形です。
h2 | Sign up = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| = render "devise/shared/error_messages", resource: resource .field = f.label :name br = f.text_field :name .field = f.label :email br = f.email_field :email, autofocus: true, autocomplete: "email" .field = f.label :password - if @minimum_password_length em | ( = @minimum_password_length | characters minimum) br = f.password_field :password, autocomplete: "new-password" .field = f.label :password_confirmation br = f.password_field :password_confirmation, autocomplete: "new-password" .actions = f.submit "Sign up" = render "devise/shared/links"
ここからpassword_fieldとpassword_confirmationを削除してしまいます。
削除した後のapp/views/devise/registrations/new.html.slim
は下記のようになりました。
h2 | Sign up = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| = render "devise/shared/error_messages", resource: resource .field = f.label :name br = f.text_field :name .field = f.label :email br = f.email_field :email, autofocus: true, autocomplete: "email" .actions = f.submit "Sign up" = render "devise/shared/links"
実際の画面はこんな感じです。
コントローラー側の変更を行う
今回はRegistrationsControllerに登録処理を渡したいので、createの部分をオーバーライドします。 createメソッドのコメントアウトを外して、How-toで紹介されていた
generated_password = Devise.friendly_token.first(8) user = User.create!(:email => email, :password => generated_password) RegistrationMailer.welcome(user, generated_password).deliver
を入れたいと思います。
controllerに記載するとこんな感じ。createの部分を今回のコードに書き換えています。
controllers/users/registrations_controller.rb
# frozen_string_literal: true class Users::RegistrationsController < Devise::RegistrationsController before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] # GET /resource/sign_up # def new # super # end # POST /resource def create generated_password = Devise.friendly_token.first(8) user = User.create!(email: params[:user][:email], password: generated_password, name: params[:user][:name]) RegistrationMailer.welcome(user, generated_password).deliver end # GET /resource/edit # def edit # super # end # PUT /resource # def update # super # end # DELETE /resource # def destroy # super # end # GET /resource/cancel # Forces the session data which is usually expired after sign # in to be expired now. This is useful if the user wants to # cancel oauth signing in/up in the middle of the process, # removing all OAuth session data. # def cancel # super # end # protected # If you have extra params to permit, append them to the sanitizer. def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end # If you have extra params to permit, append them to the sanitizer. # def configure_account_update_params # devise_parameter_sanitizer.permit(:account_update, keys: [:attribute]) # end # The path used after sign up. # def after_sign_up_path_for(resource) # super(resource) # end # The path used after sign up for inactive accounts. # def after_inactive_sign_up_path_for(resource) # super(resource) # end end
※このままではまだ動かないので注意してください。 ついでに今回のコードが何をしているのか中身を解読します。
generated_password = Devise.friendly_token.first(8)
一行目はDevise.friendly_tokenです。 ソースはこちら。base64方式でハッシュ値を生成します。
def self.friendly_token(length = 20) # To calculate real characters, we must perform this operation. # See SecureRandom.urlsafe_base64 rlength = (length * 3) / 4 SecureRandom.urlsafe_base64(rlength).tr('lIO0', 'sxyz') end
今回はfirst(8)なので、生成された20文字のハッシュ値から先頭の8文字を取り出したものを変数に格納します。 今回のHow-toでやりたいことのコアな部分はbase64方式でランダムなハッシュ値を内部で生成し、passwordにそれを適用することでユーザーのパスワード入力の手間を省きたいということですね。 二行目は
user = User.create!(:email => email, :password => generated_password)
です。ここはdevise関係ないですね。ユーザーを作成しています。ただし、今回の構成ではnameというカラムが存在し、パラメーターの渡し方も少し異なるので、以下のように変更します。
user = User.create!(email: params[:user][:email], password: generated_password, name: params[:user][:name])
入力フォームから渡されたnameとemailを反映させたいので、params経由でデータを取得します。passwordは先ほど生成した値が入った変数generated_passwordを指定します。ついでにロケット記法をやめておきましょう。
Welcomeメールを送るようにする
3行目
RegistrationMailer.welcome(user, generated_password).deliver
ですが、これはdeviseの機能でもなんでもなくメール送信です。新規登録時に生成されたパスワードとwelcomeメッセージを送りたいので、メールの実装もついでにやっておきましょう。
まずはローカルでメールを確認できるようにするために、letter_opener(_web)
というgemを入れます。
GemFile
gem 'letter_opener_web'
このようにGemfileに記載し、
$ bundle install
インストールできたら、確認用のルーティングを生成したいのでroutes.rb
に
if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end
を記載します。全体像は以下のようになりました。
routes.rb
Rails.application.routes.draw do devise_for :users, controllers: { registrations: 'users/registrations', passwords: "users/passwords" } root to: 'home#index' get 'home/index' resource :mypages # 以下を追記 if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end end
最後に、debelopment.rbに
config.action_mailer.delivery_method = :letter_opener_web
を追記します。
Rails.application.configure do ~中略 config.assets.quiet = true config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } config.action_mailer.delivery_method = :letter_opener_web #これを追記 end
これでletter_opener_web
が
http://localhost:3000/letter_opener
で確認できるようになりました。ローカル環境でメールを確認する一般的な方法です。
続いてメールの実装です。How-toではRegistrationMailer
のwelcomeメソッドをdeliverしているので、今回はそれに従います。(別名でも全然大丈夫です。)
$ rails g mailer registration welcome
これでRegistrationMailer
というメーラークラスが作成されます。またコマンドラインでwelcomeを渡しているので、welcomeというメソッドを自動で定義してくれています。
class RegistrationMailer < ApplicationMailer # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.registration_mailer.welcome.subject # def welcome @greeting = "Hi" mail to: "to@example.org" end end
こんな感じになってるはずです。 一度How-toにあったメールの実装に戻りましょう。
RegistrationMailer.welcome(user, generated_password).deliver
実装によるとuserインスタンスとgenerated_passwordを引数に渡すような仕様になっているようです。 今回はメールでパスワードを送りたいので、それっぽく書き換えてみましょう。
class RegistrationMailer < ApplicationMailer # Subject can be set in your I18n file at config/locales/en.yml # with the following lookup: # # en.registration_mailer.welcome.subject # def welcome(user, password) @user = user @password = password mail ( subject: "ようこそ", to: user.email ) end end
引数を二つとるようにし、送信先を登録したemailに変更します。メールタイトルは今回「ようこそ」となるようにしました。
本文のカスタマイズ
先ほど実行した下記のコマンドによって、いくつかファイルが自動生成されます。
コマンド
$ rails g mailer registration welcome
生成されるファイル
app/views/registration_mailer/welcome.text.slim app/views/registration_mailer/welcome.html.slim
この中の記載がメール本文に当たります。
デフォルトはこんな感じ。
app/views/registration_mailer/welcome.html.slim
h1 Registration#welcome p = @greeting + ", find me in app/views/registration_mailer/welcome.html.slim"
今回は登録したユーザーの名前と、自動生成したパスワードを表記する構成にします。
app/views/registration_mailer/welcome.html.slim
= @user.name | さん p あなたのパスワードは = @password |です
こんな感じでシンプルに書き換えました。 では実際に会員登録です。
会員登録後の処理
ここまで正常に実装できていれば、新規登録した場合、レコードが保存され、メールが飛んできます。 今回はsample_user1という名前、emailアドレスはsample@test.comで登録しました。
subjectに「ようこそ」という文字が表記され、登録された名前とpasswordが表示されれば成功です。 一点だけ、このままだとメールを送っただけで何も処理を行っていないので、ユーザーはこのままだと置いてけぼりになってしまいます。 How-toにあるように
sign_in(:user, user)
でサインインしてもいいですが、今回は登録したパスワードとメールアドレスが使えるのか試したいので、sign_inはせず、loginページに飛ばしたいと思います。 registrations_controllerに処理後のリダイレクト処理を追加します。 この一行をcreateメソッドに追加します。
redirect_to new_user_session_path
追加後のregistrations_controllerの中身は下記のようになります。 controllers/users/registrations_controller.rb
# frozen_string_literal: true class Users::RegistrationsController < Devise::RegistrationsController before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] # GET /resource/sign_up # def new # super # end # POST /resource def create generated_password = Devise.friendly_token.first(8) user = User.create!(email: params[:user][:email], password: generated_password, name: params[:user][:name]) RegistrationMailer.welcome(user, generated_password).deliver redirect_to new_user_session_path #追加 end # GET /resource/edit # def edit # super # end # PUT /resource # def update # super # end # DELETE /resource # def destroy # super # end # GET /resource/cancel # Forces the session data which is usually expired after sign # in to be expired now. This is useful if the user wants to # cancel oauth signing in/up in the middle of the process, # removing all OAuth session data. # def cancel # super # end # protected # If you have extra params to permit, append them to the sanitizer. def configure_sign_up_params devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end # If you have extra params to permit, append them to the sanitizer. # def configure_account_update_params # devise_parameter_sanitizer.permit(:account_update, keys: [:attribute]) # end # The path used after sign up. # def after_sign_up_path_for(resource) # super(resource) # end # The path used after sign up for inactive accounts. # def after_inactive_sign_up_path_for(resource) # super(resource) # end end
これで会員登録後、ログインページにリダイレクトされます。 届いたパスワードを確認し、ログインできるか試してみましょう。 今回はログインできたことを確かめるためにログインユーザーの名前を表示させる一行を追加してみたいと思います。
h1 | Home#index p | Find me in app/views/home/index.html.erb = current_user&.name = link_to 'ログアウト', destroy_user_session_path, method: :delete
会員登録した後、letter_openerに記載されているパスワードと登録したemailアドレスでログインできるか試してみましょう。 ログインして下記のように表示されれば成功です!
以上、パスワード不要のログインでした。
実際はconfirmableなどを入れると思うので、メールの実装はそちらによることも多いかと思いますが、パスワード不要でログインしたいという要件があれば是非参考にしていただければと思います。