3
6

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 5 years have passed since last update.

Rails ~会員登録~

Last updated at Posted at 2019-06-22
1 / 8

これまでのあらすじ

  1. 仮登録でメールアドレスを登録
  2. 登録したメールアドレスへ本登録画面に飛ぶURLを記載したメールを送信
  3. メールでURLへ遷移
  4. 登録に必要な情報の入力
  5. 登録完了
  6. ログイン
  7. ログアウト

までやっていきたいと思います。


メール送信

前の記事でユーザ仮登録のモーダルウィンドウ表示まで実装しました。
で、登録ボタンで以下のアクションを呼びます。

top_controller.rb
  def create
    @temp_user = TempUser.create_temp_user(temp_users_params)
    respond_to do |format|
      if @temp_user.save
        UserMailer.with(temp_user: @temp_user, locale: params[:locale]).request_registration.deliver_later
        format.js { @status = "success" }
      else
        format.js { @status = "fail" }
      end
    end
  end

TempUserクラスの#create_temp_userメソッドで初期化します。

temp_user.rb
    # 仮ユーザの作成
    def create_temp_user(params)
      # temp_usersにmail_addressで検索 初期化する
      temp_user = find_or_initialize_by(mail_address: params[:mail_address])
      temp_user.last_name = params[:last_name]
      temp_user.first_name = params[:first_name]
      temp_user.token = create_token
      temp_user.expired_at = DateTime.now + 1
      return temp_user
    end

find_or_initialize_byでメールアドレスを検索します。
もし、仮登録済みで本登録していないメールアドレスの場合、後勝ちにして最後に入力した情報でUPDATEします。
もし、temp_userにない場合は、入力情報でINSERTします。
find_or_initialize_byを使用し、saveをするタイミングでINSERTかUPDATEか判定、UPSERTが実現できます。

expired_at(有効期限)は、とりあえず1日にしていますが、設定を外出ししたいですね。

saveメソッドの戻り値でSQLの成功・失敗を判定して処理を判定しています。

成功時は、登録情報を元にUserMailerでメールを作成しています。

user_mailer.rb
class UserMailer < ApplicationMailer
  default from: "hogehoge@gmail.com"

  def request_registration
    @temp_user = params[:temp_user]
    @locale = params[:locale]
    mail(to: @temp_user.mail_address, subject: I18n.t("mailers.user_mailer.request_registration.subject"))
  end
end

ApplicationMailerを継承したUserMailerです。
from:は送信元のメールアドレスを設定します。
request_registrationがメール送信の本体です。
paramsで引数を受け取ってメールを生成します。

メール本体は、viewsの下に作ります。

request_registration.html.erb
<%= stylesheet_link_tag "mailers/request_registration.css", media: "all" %>
<p class="message"><%= t('mailers.user_mailer.request_registration.dear', last_name: @temp_user.last_name, first_name: @temp_user.first_name) %></p>
<pre class="message"><%= t('mailers.user_mailer.request_registration.message_text', expired_at: l(@temp_user.expired_at, format: :default)) %></pre>
<div class="btn">
  <%= link_to t('mailers.user_mailer.request_registration.button'), {controller: 'account', action: 'regist', locale: @locale, token: @temp_user.token } %>
</div>

メソッド名のhtml.erb(HTMLメール)またはtext.erb(テキストメール)を雛形として作ります。
上記は、HTMLメールの雛形です。link_toでaccount_controller.rbのregistメソッドを指定しています。temp_user登録の際、生成したtokenをGETリクエストをパラメタとしてURLに付与し、link_toで生成しています。


メールを受信する

開発中に動作確認したいですが、実際に送ると、誤送信する恐れがあるので、以下のgemを入れます。

Gemfile
  # letter_opener_web
  gem 'letter_opener_web', '~> 1.0'

このgemはメールを送信・受信してくれます。
config/environments/development.rbに設定を追加します。

development.rb
config.action_mailer.delivery_method = :letter_opener_web

また、routes.rbに以下のパスを設定し、送信メールを確認できるようにします。

routes.rb
    # letter_opener_web
    mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?

これで(http://localhost:3000/letter_opener) でアプリが送信したメールをブラウザで確認できます。


登録

メールに記載されたURLを押下すると、登録に必要な情報を入力する画面へ遷移できます。

パスワードについて

ライブラリを使わないでログイン機能を実装するために以下のgemを使います。

Gemfile
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

Userモデルにhas_secure_passwordを追加します。

user.rb
class User < ApplicationRecord
  include ActiveModel::Validations
  has_secure_password validations: true

  validates :last_name, presence: true
  validates :first_name, presence: true
  validates :last_name_roman, upper_case_format: true, unless: Proc.new { |p| p.last_name_roman.blank? }
  validates :first_name_roman, upper_case_format: true, unless: Proc.new { |p| p.first_name_roman.blank? }
  validates :sex, inclusion: { in: [0, 1] }
  validates :birthed_on, presence: true
  validates :mail_address, uniqueness: true
  validates :password, password_format: true
  validate :already_used_mail_address

  # already used mail address
  def already_used_mail_address
    unless User.find_by(mail_address: mail_address).nil?
      errors.add(:mail_address, I18n.t("validate.already_use"))
    end
  end

  # create remember token
  def self.create_remember_token
    SecureRandom.urlsafe_base64
  end

  # encrypt
  def self.encrypt(token)
    Digest::SHA256.hexdigest(token.to_s)
  end
end

passwordとpassword_confirmation属性、さらにauthenticateメソッドが使用できるようになります。
さらにDB内ではpassword_digestというカラムで暗号化されたパスワードは保存されます。
(password_digestをmigrationで対象のテーブルに追加します。)

アプリケーションログには、入力値は当然出力されず
DBには、暗号化されたパスワード文字列が登録されます。

登録ボタンでcreateメソッドが呼び出され、登録成功したら完了画面、失敗したら再度登録画面をレンダリングします。

account_controller.rb
  def create
    @user = User.new(users_params)
    if @user.save
      render action: :complete
    else
      render action: :regist
    end
  end

ログイン・ログアウト

application_controller.rbに以下を定義します。

application_controller.rb
class ApplicationController < ActionController::Base

  # filter
  # actionの直前に実行されるfilter
  before_action :set_locale
  before_action :current_user
  before_action :require_sign_in!
  # helper methodとして使用できる
  helper_method :signed_in?

  def set_locale
    I18n.locale = locale
  end

  def locale
    @locale ||= params[:locale] ||= I18n.default_locale
  end

  def default_url_options(options = {})
    options.merge(locale: locale)
  end

  def current_user
    remember_token = User.encrypt(cookies[:remember_token])
    @current_user ||= User.find_by(remember_token: remember_token)
  end

  def sign_in(user)
    remember_token = User.create_remember_token
    # cookieにremember_tokenをsetする
    cookies.permanent[:remember_token] = remember_token
    # remember_tokenを更新する
    user.update_column(:remember_token, User.encrypt(remember_token))

    @current_user = user
  end

  def sign_out
    @current_user = nil
    # cookieのremember_tokenを削除する
    cookies.delete(:remember_token)
    redirect_to login_path
  end

  def signed_in?
    @current_user.present?
  end

  private

  def require_sign_in!
    redirect_to login_path unless signed_in?
  end
end

filter処理として
current_userメソッド
ログイン時にCookieとDBに登録したトークン情報を突き合わせて、ユーザ情報を取得する
require_sign_in!メソッド
ユーザ情報が取得できない場合(=ログインしていない場合)ログイン画面へリダイレクトする

これをapplication_controller.rbに定義することによりapplication_controllerを継承する全てのcontrollerにこのfilter処理が走ります。

これにより画面上でログイン状態・非ログイン状態を判断するわけです。

ログイン処理はsession_controller.rbに定義しています。

session_controller.rb
class SessionsController < ApplicationController
  # filter
  # actionの直前に実行されるfilterをskipする
  skip_before_action :require_sign_in!, only: [:new, :create]
  # actionの直前に実行されるfilter
  before_action :set_user, only: [:create]

  # GET /login
  def new
    redirect_to root_path
  end

  # PUT /login
  def create
    if @user.authenticate(@session.password)
      sign_in(@user)
    else
      @session.sign_in_failure
    end
    render "top/index"
  end

  # DELETE /logout
  def destroy
    sign_out
    redirect_to login_path
  end

  private

  def set_user
    @session = Session.new(session_params)
    if !@session.valid?
      render "top/index" and return
    end
    @user = User.find_by!(mail_address: @session.mail_address)
  rescue
    @session.sign_in_failure
    render "top/index"
  end

  def session_params
    params.require(:session).permit(:mail_address, :password)
  end
end

Sessionモデルはmail_addressとpasswordのログイン認証に必要な情報をもっているクラスです。

skip_before_actionは、actionの直前に実行されるfilterをskipするための宣言です。
当然ログイン画面表示とログイン処理にログイン認証のfilterが入っているとログインできないため、skipしています。

createメソッドの前のみset_userのfilterが適用されます。

set_userメソッドは画面から入力されたフォーム情報を元にmail_addressでユーザ情報を取得します。
取得できない場合、例外を発生させます。例外発生時はログイン画面をレンダリングします。

createメソッドは、set_userメソッドで取得したユーザ情報とパスワードを確認します。
確認できた場合、ログイン処理(application_controller.rbに定義)をします。

ログイン処理は、ログイン認証していることを示す、remember_tokenを画面に保持します。
remember_tokenはUserクラスで定義したメソッドでランダム文字列で生成されます。
それをCookieに保存し、その後DB(userテーブル)UPDATEします。

前述のログイン確認のfilterはこのremember_tokenを元にログインしているか、していないかを判断するというわけです。


終わりに

  • 勉強会の資料なので、急いで作ったので、もうちょっと修正します。
  • 参考リンクとかも記載せねば…

参考

工事中…

3
6
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
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?